Misskey & Webテクノロジー最前線

CSS Variablesはここまで進化した

本連載は分散型マイクロブログ用ソフトウェアMisskeyの開発に関する紹介と、関連するWeb技術について解説を行っています。

Misskeyでは2025年も大きな機能改修・リファクタリングに取り組んでいます。

その中にCSS Variablesに関する作業もあったので、今回はその紹介も兼ねてMisskeyで使っている便利なテクニックなどを取り上げます。

CSS Variablesとは

CSS Variables(CSS変数)は、カスタムプロパティとも呼ばれ、CSSで変数を使用できる機能です。

CSS変数を使うと、ウェブサイト内で共通して使われるテーマカラーなどの値を使いまわしたり、動的にプロパティの値を変更することが可能です。さらに、CSSで用意されている色関数と組み合わせると、同じく動的に相対的な色の定義が行え、表現の幅を大きく広げることが可能です。

MisskeyのWebクライアントには「テーマ」機能があり、ユーザーが好みに応じて見た目を変更できます。このテーマではいろいろな個所の色を指定できますが、その機能実装でCSS変数を活用しています。

Misskeyのテーマ機能

プリプロセッサとの違い

昔から、SassといったCSSプリプロセッサには同様の概念がありましたが、CSS変数はCSSネイティブの機能です。

プリプロセッサの変数は値があらかじめ静的にコンパイルされますが、CSS変数はランタイムで動的に値を変えることができます。つまり、プリプロセッサでは変数の値を事前に定義しておく必要があります。そのため、⁠ユーザーが自由に色を変更できるテーマ機能」といったコンパイル時にわからない情報をCSSで利用したい場合は、プリプロセッサの変数は使うことができません。振る舞いとしては、変数というよりマクロに近いかもしれません。

一方CSS変数は真にランタイムで動作しますので、そういった制約はなく、CSSだけで完結するのでJavaScriptも不要です。

使い方

変数は次のように定義します。

--変数名: 値;

定義した変数は次のように参照します。

var(--変数名)

もっとも単純な例は以下のようになります。

.myText {
  --myColor: #9be619;
  color: var(--myColor);
}

ルートスコープに変数を定義して、ウェブサイト全体で参照することもできます。

:root {
  --myColor: #9be619;
}

.myText {
  color: var(--myColor);
}

.myButton {
  background: var(--myColor);
}

変数の値をJavaScriptで動的に定義することもできます。

document.documentElement.style.setProperty('--radius', '20px');
.rounded {
  border-radius: var(--radius);
}

変数はそのままプロパティの値に指定するだけでなく、式の中で参照することも可能です。

.veryRounded {
  border-radius: calc(var(--radius) * 2);
}

.veryVeryRounded {
  border-radius: calc(var(--radius) * 4);
}

Relative color syntax

CSS変数が真価を発揮するのは、Relative color syntax(相対色構文)と組み合わせたときであると筆者は勝手に思っています。

相対色構文を使うと、CSS変数を元にして別の色を宣言的に作成できます。例えば、元の色を明るく/暗くしたり、色相をずらしたり、透明度を変更したりできます。

いままで

これは昔話ですが、今までそのようにCSS変数の色を相対的に変更したい時は、色の成分をr, g, bやh, s, lに分割した変数にしておき、それらを使用時に組み合わせるというテクニックが使われていました。

例を以下に示します。

:root {
  --themeColor-h: 82deg;
  --themeColor-s: 80%;
  --themeColor-l: 50%;
}

.themeColorBackground {
  background: hsl(var(--themeColor-h), var(--themeColor-s), var(--themeColor-l));
}

.themeColorBackgroundDarken {
  background: hsl(var(--themeColor-h), var(--themeColor-s), calc(var(--themeColor-l) - 10%));
}

.themeColorBackgroundLighten {
  background: hsl(var(--themeColor-h), var(--themeColor-s), calc(var(--themeColor-l) + 10%));
}

このような記述で、テーマカラーの少し暗いバージョンや少し明るいバージョンを動的に生み出すことができます。ただ見ての通り、あらかじめ色の成分を3つの変数に分離しておく必要があり、記述が冗長になってしまいます。

また、透明度を変更したい場合は、rgba関数を使って以下のように実現していました。

:root {
  --themeColor-rgb: 155, 230, 25;
}

.themeColorBackgroundHalfTransparent {
  background: rgba(var(--themeColor-rgb), 0.5);
}

このように、明るさや透明度を操作したい場合、一つの色でもr, g, b版、h, s, l版などに分けなければならず面倒でした。

以前のMisskeyのテーマ実装では、JavaScriptでテーマ構文を解析した上で色を操作し、別のテーマ変数として追加するなどの煩雑な処理を行っていました。

これから

そこで相対色構文の出番です。相対色構文を使うと以下のように書けます。

:root {
  --themeColor: #9be619;
}

.themeColorBackground {
  background: var(--themeColor);
}

.themeColorBackgroundDarken {
  background: hsl(from var(--themeColor) h s calc(l - 10)); /* 輝度(l)を下げて少し暗くする */
}

.themeColorBackgroundLighten {
  background: hsl(from var(--themeColor) h s calc(l + 10)); /* 輝度(l)を上げて少し明るくする */
}

.themeColorBackgroundHalfTransparent {
  background: hwb(from var(--themeColor) h w b / 0.5); /* alphaを0.5にして透明度を半分にする */
}

hsl などの色関数に、fromキーワードで元の色を指定し、calcでそれぞれの成分を操作することができます。

色の成分ごとに変数を分ける必要がなくなったため、ひとつの色を定義するのに変数がひとつで済み、とてもシンプルになっています。

Container style queries

Container style query(スタイルクエリ)を使うとCSSプロパティの値による条件分岐ができます。

使用例を示します。

.myText {
  color: #000;
}

@container style(background: #000) {
  .myText {
    color: #fff;
  }
}

メディアクエリに似た構文で、条件となるプロパティとその値を指定します。上記の例では、backgroundが黒の場合だけ文字色を白に変えています。

……と、ここで鋭い方は気づいたかもしれませんが、CSS変数はカスタムプロパティという別名通りプロパティの一種なので、スタイルクエリの条件指定に使用可能です。

この組み合わせもなかなかに強力です。

.myText {
  color: #000;
}

@container style(--myBg: #000) {
  .myText {
    color: #fff;
  }
}

さらに、当然と言えば当然ですが条件となるプロパティの値にもCSS変数は参照できます。 このことを利用して、Misskeyでは「CSS変数AとBの色が同じである場合だけ区切り線を表示する」といったスタイルを実現しています。

例を以下に示します。

.header {
  background: var(--headerBg);
}

.body {
  background: var(--bodyBg);
}

@container style(--headerBg: var(--bodyBg)) {
  .header {
    border-bottom: solid 1px #000;
  }
}

headerとbodyがあり、それぞれの背景色をテーマ変数で設定可能になっていますが、もしどちらの背景色も同じに設定されると、見た目上headerとbodyの境界が分からなくなります。そこでスタイルクエリを使用して、headerBg変数とbodyBg変数が同じ色」の場合だけ適用される境界線のスタイルを定義しています。

スタイルクエリを使用した境界線のスタイルの変更

注意 本記事執筆時点では、ChromeとSafariが対応しており、Firefoxでは未対応となっています。

まとめ

今回はCSS VariablesやRelative color syntax、Container style queryとその活用方法について紹介しました。

現在CSSだけで、変数を使えたり変数の値に応じた条件分岐まで可能になっています。使いこなせばCSSによる表現の幅がぐっと広がりますので、ぜひ活用してみてください。

おすすめ記事

記事・ニュース一覧