CSS Variables(カスタムプロパティ): なぜ、関心を持つべきか?

(編注:2016/7/29、頂いたフィードバックを元に記事を修正いたしました。)

CSS Variables、もっと正確に言うとCSSカスタムプロパティが、Chrome 49でサポートされました。CSS Variables は、CSSで値の繰り返しを減らすのに有効です。また、テーマの切り替えなどの実行中の強力なエフェクトや、将来のCSS機能をもしかすると拡張/ポリフィルすることにも役立ちます。

CSSの散乱

アプリケーションを設計するとき、アプリケーションの一貫した外観を維持するために再利用される独自のカラーセットを設定するのが一般的です。残念ながら、このようなカラー値をCSSに何度も何度も繰り返し設定することは面倒なだけでなく、エラーが発生しやすくなります。カラーを何カ所か変更する必要がある場合、思い切って、全部「検索して置換」することもできますが、大きなプロジェクトでは、危険な事態を招きかねません。

近年多くの開発者が、プリプロセッサ変数を用いて問題を解決する、SASSあるいはLESSなどのCSSプリプロセッサに頼ってきました。こういったツールは開発者の生産性を非常に高めてきましたが、この変数には重大な欠点があります。それは、変数が静的なので、実行時に変更できないことです。実行時に変数を変えられるようになると、アプリケーションのテーマを動的に変えるようなことが可能になるだけでなく、レスポンシブデザインや将来のCSS機能をポリフィルする可能性に大きな影響を与えます。 Chrome 49のリリースにより、CSSのカスタムプロパティという形で、このようなことが可能になったのです。

カスタムプロパティの要点

カスタムプロパティによって、私たちのCSSツールボックスに2つの新しい機能が追加されます。

  • 作成者が、自分で名づけたプロパティに任意の値を割り当てることができる。
  • var()関数を使って、作成者が上記の値を他のプロパティで利用することができる。

以下に簡単な実例を示します。

:root {
  --main-color: #06c;
}

#foo h1 {
  color: var(--main-color);
}

--main-colorは作成者が定義したカスタムプロパティで、#06Cという値を持ちます。全てのカスタムプロパティは、2つのハイフンで始まることに注意してください。

var()関数は、自分の値をカスタムプロパティの値に置き換えます。その結果、color: #06c;となります。カスタムプロパティがスタイルシートに定義されていれば、var関数を利用することができます。

最初は、構文がおかしいように感じるかもしれません。「変数名には$fooを使えばいいじゃないか」と言う開発者もたくさんいます。しかしこのアプローチは、柔軟性をできる限り高め将来的に$fooのマクロにも対応できるようにするために、明確に選択されたものなのです。この話の背景が知りたい方は、この仕様の立案者の1人であるTab Atkinsによるこの記事を読んでみてください。

カスタムプロパティの構文

カスタムプロパティの構文は簡潔です。

--header-color: #06c;

カスタムプロパティは大文字と小文字が区別されるため、--header-color--Header-Colorは異なるカスタムプロパティになるということに注意しましょう。外見上は簡素に見えるかもしれませんが、実際カスタムプロパティは非常に幅広い構文を許容します。例えば、下記のようなカスタムプロパティも有効です。

--foo: if(x > 5) this.width = 10;

これは通常のどのプロパティの中でも無効なので変数としては役に立ちませんが、潜在的には可読であり、JavaScriptを使えば実行時に有効になります。これはつまり、カスタムプロパティには、現在のCSSプリプロセッサにはできない全ての興味深い技術への道をひらく力があるということです。ですから、「はあ、SASSがあるのに誰がカスタムプロパティなんて…」と思っている方がいたら、もう一度よく考えてみてください。これは使い慣れた変数ではないのです。

カスケード

カスタムプロパティは標準的なカスケード規則にのっとっているため、様々な詳細度のレベルで同じプロパティを定義できます。

:root { --color: blue; }
div { --color: green; }
#alert { --color: red; }
* { color: var(--color); }

<p>I inherited blue from the root element!</p>
<div>I got green set directly on me!</div>
<div id="alert">
  While I got red set directly on me!
  <p>I’m red too, because of inheritance!</p>
</div>

これは、メディアクエリ内でカスタムプロパティを使いレスポンシブデザインが作れるということを意味しています。ユースケースの1つとしては、画面サイズが大きくなる時に主要なsection 要素まわりのmarginを拡大するといったことでしょうか。

:root {
  --gutter: 4px;
}

section {
  margin: var(--gutter);
}

@media (min-width: 600px) {
  :root {
    --gutter: 16px;
  }
}

重要なこととして伝えておきたいのは、メディアクエリ内で変数を定義できない現在のCSSプリプロセッサでは、上記のコードスニペットは使えないということです。この能力があれば、多くの可能性への道が開けるのです。

また、他のカスタムプロパティから値を引き出すカスタムプロパティを持つこともできます。これはテーマを作るのに非常に役立ちます。

:root {
  --primary-color: red;
  --logo-text: var(--primary-color);
}

var()関数

カスタムプロパティの値を取り出して使うには、var()関数を使用する必要があります。var()の構文は以下のようになります。

var(<custom-property-name> [, <declaration-value> ]? )

ここでは、<custom-property-name>は作成者が定義したカスタムプロパティの名前(--fooなど)で、<declaration-value>は参照されたカスタムプロパティが無効だった場合に使われるフォールバック値です。フォールバック値はカンマ区切りのリストとして書けるので、それが結合されて単一の値として扱われるようになります。例えばvar(--font-stack, "Roboto", "Helvetica");"Roboto", "Helvetica"というフォールバックを定義します。marginやpaddingで使われるような簡略表記の値はカンマ区切りではありませんので、paddingの適切なフォールバックは以下のようになります。

p {
  padding: var(--pad, 10px 15px 20px);
}

このようなフォールバック値を使えば、コンポーネント作成者はさまざまな環境に対応できるよう、要素を記述することができます。

/* In the component’s style: */
.component .header {
  color: var(--header-color, blue);
}
.component .text {
  color: var(--text-color, black);
}

/* In the larger application’s style: */
.component {
  --text-color: #080;
  /* header-color isn’t set,
     and so remains blue,
     the fallback value */
}

カスタムプロパティはShadow DOMの境界にアクセスできるので、Shadow DOM を使うWeb Componentのテーマを作成するとき、特に便利です。Web Componentの作成者は、フォールバック値を使って初期のデザインを作ることができ、”フック”のテーマをカスタムプロパティの形式で公開することができます。


<x-foo>
  #shadow
    <style>
      p {
        background-color: var(--text-background, blue);
      }
    </style>
    <p>
      This text has a yellow background because the document styled me! Otherwise it
      would be blue.
    </p>
</x-foo>
/* In the larger application's style: */
x-foo {
  --text-background: yellow;
}

var()を使う場合は、いくつか注意しなければいけないことがあります。プロパティ名を変数にすることはできません。下の例を見てください。

.foo {
  --side: margin-top;
  var(--side): 20px;
}

これは、margin-top: 20px;という設定と同等ではありません。それどころか、2つ目の宣言は無効で、エラーとして返されてしまいます。

同じように、値の一部を(単純に)変数で構築することはできません。

.foo {
  --gap: 20;
  margin-top: var(--gap)px;
}

この場合も同じで、margin-top: 20px;という設定と同等ではありません。値を構築するためには、calc()関数が必要です。

calc()関数で値を構築する

今までにcalc()関数を使った経験がない人にとって、calc()関数は、CSSの値を決める計算を可能にする、使いやすい小さなツールです。全ての最新ブラウザでサポートされていて、カスタムプロパティと組み合わさって新しい値を構築できます。以下はその例です。

.foo {
  --gap: 20;
  margin-top: calc(var(--gap) * 1px); /* niiiiice */
}

JavaScriptでカスタムプロパティを使う

実行時のカスタムプロパティの値を得るために、計算済みCSSStyleDeclarationオブジェクトのgetPropertyValue()メソッドを使います。

/* CSS */
:root {
  --primary-color: red;
}

p {
  color: var(--primary-color);
}
<!-- HTML -->
<p>I’m a red paragraph!</p>
/* JS */
var styles = getComputedStyle(document.documentElement);
var value = String(styles.getPropertyValue('--primary-color')).trim();
// value = 'red'

同じように、実行時のカスタムプロパティの値を設定するために、CSSStyleDeclarationオブジェクトのsetProperty()を使います。

/* CSS */
:root {
  --primary-color: red;
}

p {
  color: var(--primary-color);
}
<!-- HTML -->
<p>Now I’m a green paragraph!</p>
/* JS */
document.documentElement.style.setProperty('--primary-color', 'green');

setProperty()を呼び出すときに、var()関数を使うと、実行時に別のカスタムプロパティを参照してカスタムプロパティの値を設定することもできます。

/* CSS */
:root {
  --primary-color: red;
  --secondary-color: blue;
}
<!-- HTML -->
<p>Sweet! I’m a blue paragraph!</p>
/* JS */
document.documentElement.style.setProperty('--primary-color', 'var(--secondary-color)');

カスタムプロパティはスタイルシートの中で別のカスタムプロパティを参照できるため、これがどのように、実行時の興味深い効果を導き出すのか想像できるのではないでしょうか。

ブラウザのサポート

現在は、Chrome 49、Firefox 42、Safari 9.1、iOS Safari 9.3がカスタムプロパティをサポートしています。

デモ

サンプルを試してみてください。カスタムプロパティのおかげで利用できるようになった、面白い技術の全てを垣間見ることができます。

参考資料

カスタムプロパティをさらに詳しく学びたい場合は、Google アナリティクス チームのPhilip Waltonが書いた初心者向けの記事『why I’m Excited About Native CSS Variables(なぜ、私はネイティブなCSS Variablesに熱狂するのか)』をお読みください。また、その際に別のブラウザでchromestatus.comのタブを開いておけば、記事に合わせてその内容を確認することができます。