SassやLessなどのプリプロセッサーは、CSSのコードベースを、メンテナンスしやすい整った状態にするのに役立ちます。変数、mixin(ミックスイン)、loop(ループ)などCSSコーディングに動的な機能を追加すると、繰り返し処理を最小限にし、開発時間を短縮できます。
CSSの一部として動的な機能がいくつか登場しています。CSS変数(CSS variables:カスタムプロパティ)はブラウザーの対応状況もとてもよく、すでに利用可能です。「CSS版mixin」は準備段階です。
この記事では、CSS開発ワークフローにCSS変数を組み入れる方法を紹介します。スタイルシートのメンテナンス性が向上し、DRYの原則(繰り返しを避けること)に沿ったスタイルシートを実現します。
CSS変数とは
プログラミング言語を使った経験がある人にとって、変数のコンセプトはおなじみでしょう。変数にはプログラムの動作に必要な値を格納します。変数の値は更新可能です。
たとえば次のJavaScriptスニペットでは、
let number1 = 2;
let number2 = 3;
let total = number1 + number2;
console.log(total); // 5
number1 = 4;
total = number1 + number2;
console.log(total); // 7
2つの変数「number1」と「number2」があり、数値「2」と「3」が格納されています。
totalも変数で、number1とnumber2の合計「5」が格納されています。変数の値は動的に変更でき、プログラム内の任意の場所で更新された値を使えます。上のスニペットでは、number1の値を4に変更したのち同じ変数(number1とnumber2)を使い、加算しています。結果、格納された数字がtotalが5から7に変わりました。
変数のメリットは、値を格納したあと用途に応じて値を変更できることです。別の変数をもつ新しいエンティティをプログラムの各所に追加する必要はありません。値の変更はすべて同じ変数で実行します。
CSSは動的な機能を持たない宣言型言語です。CSSに変数が登場すると、理論ずくめで考える人はフロントエンド開発の定義が覆るじゃないかと感じるかもしれませんね。Web関連の技術は生きもののように、取り巻く環境や開発現場のニーズに応じて発展、適応します。CSSも例外ではありません。
変数はCSSの世界で注目され、発展し、現実になったのです。このすばらしい新技術をマスターして使いこなすのはとても簡単です。
CSS変数を使うメリット
CSSで変数を使うメリットは、ほかのプログラム言語で変数を使うのと大きな違いはありません。
仕様では、次のように説明されています。
CSS変数を使うことで、無意味に見える値に分かりやすい名前をつけられるため、サイズの大きいファイルの読み取りが容易になります。値の変更はカスタムプロパティ(CSS変数)で1回すればよく、変更はその変数が使われている箇所すべてに伝わります。ファイルの編集が簡単になり、エラーも少なくなります。
W3C仕様書より
以下のように言い換えられます。
変数にプロジェクトに関連した分かりやすい名前をつけることにより、コードの管理やメンテナンスがしやすくなります。プロジェクトで原色を編集する場合、CSSカスタムプロパティ--primary-colorの値を変更すればよく、すべての場所のCSSプロパティの値を変更する必要はなくなるのです。
CSS変数とプリプロセッサー変数の違い
Webサイトのスタイリングで変数の持つ柔軟性を利用する方法は、SassやLessといったプリプロセッサーの活用です。
プリプロセッサーで変数を設定すると、関数やループ、算術演算などで使えます。CSS変数に大きなメリットはないのでしょうか?
そうとは言えません。CSS変数にはプリプロセッサーで変数を設定するとは違った面がいくつかあります。
CSS変数がブラウザーで実行する動的なCSSプロパティなのに対し、プリプロセッサーの変数はCSSコードにコンパイルされるためブラウザーは関与しません。
つまり、スタイルシートドキュメント内のCSS変数は、インラインスタイル属性やSVGのプレゼンテーション属性で変更したり、JavaScriptで選択、操作したりできるのです。可能性が無限に広がります。プリプロセッサーの変数では実現できません。
CSS変数かプロセッサーの変数か、選ぶ必要はありません。CSS変数とプリプロセッサーの変数、両方とも利用しない手はないでしょう。
CSS変数の構文
分かりやすくするために「CSS変数(CSS variables)」と呼んでいますが、公式では「カスケード変数のためのCSSカスタムプロパティ(CSS custom properties for cascading variables)」としています。CSSカスタムプロパティ部分のコードは以下の通りです。
--my-cool-background: #73a4f4;
カスタムプロパティは冒頭にハイフンを2つ記述し、通常のCSSプロパティと同様に値を設定します。上のスニペットでは、カラー値にカスタムプロパティ--my-cool-backgroundを設定しました。
カスケード変数部分では、var()関数でカスタムプロパティを適用します。
var(--my-cool-background)
カスタムプロパティのスコープはCSSセレクタ内です。var()はCSSプロパティの実際の値として使えます。
:root {
--my-cool-background: #73a4f4;
}
/* The rest of the CSS file */
#foo {
background-color: var(--my-cool-background);
}
上のスニペットで、カスタムプロパティ--my-cool-backgroundのスコープは疑似クラス:rootで、値はグローバルに利用できます(つまり <html>要素内のすべてに適用できます)。これでID fooをもつコンテナのbackground-colorプロパティにvar()関数で値を適用でき、背景色がライトブルーになります。
さらに、var(--my-cool-background)を使ってカスタムプロパティ値を取得し、CSSプロパティに適用するだけで、ライトブルーのカラー値をcolor、border-colorといった複数のHTML要素のカラープロパティに設定できます。ただし、複雑化しないうちにCSS変数の命名規則を決めることをおすすめします。
p {
color: var(--my-cool-background);
}
CSS変数の値に別のCSS変数を設定することも可能です。以下に例を示します。
--top-color: orange;
--bottom-color: yellow;
--my-gradient: linear-gradient(var(--top-color), var(--bottom-color));
上のスニペットでは変数--my-gradientを作成し、値を変数--top-colorと--bottom-color双方で設定してグラデーションを実現しています。これで変数の値を変更すれば、グラデーションを修正できます。スタイルシート全体からグラデーションインスタンスをすべてピックアップする必要はないのです。
CodePenのライブデモはこちらです。
CSS変数にはフォールバック値を1つ以上設定できます。以下に例を示します。
var(--main-color, #333);
上のスニペットでは「#333」がフォールバック値です。フォールバック値を記述しないと、カスタムプロパティが無効だったり設定されていなかったりすると、代わりに継承値が適用されます。
CSS変数では大文字と小文字が区別される
通常のCSSプロパティとは異なり、CSS変数では大文字と小文字が区別されます。
たとえば、var(--foo)とvar(--FOO)は別のカスタムプロパティを参照します。
CSS変数はカスケードの対象となる
通常のCSSプロパティと同様、CSS変数は継承されます。カスタムプロパティを定義し、値をblueとします。
:root {
--main-color: blue;
}
<html>ルート要素内で変数--main-colorが適用された要素はすべて値としてblueを継承します。
ほかの要素内でカスタムプロパティに別の値が新しく設定された場合、要素の子要素はすべて新しい値を継承します。以下に例を示します。
:root {
--main-color: blue;
}
.alert {
--main-color: red;
}
p {
color: var(--main-color);
}
<--! HTML -->
<html>
<head>
<!-- head code here -->
</head>
<body>
<div>
<p>blue paragraph.</p>
<div class="alert">
<p>red paragraph.</p>
</div>
</div>
</body>
</html>
上のマークアップの最初の段落はグローバル変数--main-colorから値を継承し、カラーは青になります。
.alertクラスのdiv内の段落のカラーは、ローカルにスコープ指定された変数--main-colorから値を継承しているため、--main-colorの値の赤になります。
ルールは以上です。コーディングに進みます。
SVGでCSS変数を使うには
CSS変数とSVGの相性は抜群です。CSS変数を使って、インラインSVG内のスタイルとプレゼンテーション属性の双方を変更できます。
アイコンが配置された親コンテナごとにSVGアイコンのカラーを変更します。変数のスコープを親コンテナに限定し、それぞれに希望の色を設定すると、各コンテナのアイコンは親コンテナの色を継承します。
関連するスニペットは以下の通りです。
/* inline SVG symbol for the icon */
<svg>
<symbol id="close-icon" viewbox="0 0 200 200">
<circle cx="96" cy="96" r="88" fill="none" stroke="var(--icon-color)" stroke-width="15" />
<text x="100" y="160" fill="var(--icon-color)" text-anchor="middle" style="font-size:250px;">x</text>
</symbol>
</svg>
/* first instance of the icon */
<svg>
<use xlink:href="#close-icon" />
</svg>
上のマークアップでは<symbol>タグで、SVG画像を非表示にします。続いて<use>タグで画像を表示します。この方法では<symbol>要素のIDを使って(#close-icon)参照するだけで、アイコンを複数作成でき、それぞれカスタマイズできます。画像の数だけコードを繰り返し記述するより便利です。詳しくは、Build Your Own SVG Icons(オリジナルのSVGアイコン作成)が参考になります。
SVGの「symbol」要素の「circle」要素内、strokeプロパティの値と「text」要素内のfillプロパティの値を見てください。どちらもCSS変数--icon-colorが適用されています。このCSS変数はCSSドキュメント内の:rootセレクタで定義しています。
:root {
--icon-color: black;
}
アイコンは次のように表示されます。
SVGアイコンを複数のコンテナ要素内に配置し、変数を個々の親要素のセレクタ内にローカル化して別のカラー値を設定すると、スタイルルールを追加しなくてもアイコンの色を変えられます。
.successクラスのdiv内に同じアイコンのインスタンスを配置します。
.successセレクタ内で値にgreenを設定すると変数--icon-colorをローカル化し、結果を確認します。
アイコンが緑になりました。
全体のデモです。
@keyframesアニメーションでCSS変数を使う
CSS変数は、通常のHTML要素やインラインSVG要素のCSSアニメーションにも使えます。アニメーションの要素を、ターゲットとするセレクタ内でカスタムプロパティを定義して@keyframesブロック内で関数var()を使ってカスタムプロパティを参照するだけです。
SVG画像内で.bubbleクラスの<ellipse>要素をアニメーションしてみましょう。
.bubble {
--direction-y: 30px;
--transparency: 0;
animation: bubbling 3s forwards infinite;
}
@keyframes bubbling {
0% {
transform: translatey(var(--direction-y));
opacity: var(--transparency);
}
40% {
opacity: calc(var(--transparency) + 0.2);
}
70% {
opacity: calc(var(--transparency) + 0.1);
}
100% {
opacity: var(--transparency);
}
}
CSSのcalc()を使って関数var()と計算しています。コードの柔軟性がさらに向上します。
この場合、CSS変数を使うメリットは、適切なセレクタ内で変数の値を変更するだけでアニメーションを微調整できることです。@keyframesで指定したすべての部分に含まれるプロパティを探し出す必要はないのです。
完成したデモで試してください。
JavaScriptでCSS変数を操作
CSS変数は、JavaScriptコードから直接アクセスできます。
値が100px、スコープはCSSドキュメント内の.sidebarクラスのCSS変数--left-posがあるとします。
.sidebar {
--left-pos: 100px;
}
JavaScriptコードから--left-posの値を取得できます。
// cache the element you intend to target
const sidebarElement = document.querySelector('.sidebar');
// cache styles of sidebarElement inside cssStyles
const cssStyles = getComputedStyle(sidebarElement);
// retrieve the value of the --left-pos CSS variable
const cssVal = String(cssStyles.getPropertyValue('--left-pos')).trim();
// log cssVal to print the CSS
// variable's value to the console: 100px
console.log(cssVal);
JavaScriptでCSS変数を設定できます。
sidebarElement.style.setProperty('--left-pos', '200px');
上のスニペットでは、サイドバー要素用の変数--left-posの値を200pxに設定しています。
CSS変数でWebページにインタラクティビティを持たせる方が、状況に応じて多くのクラスを切り替えたり、CSSルール全体を繰り返し記述したりするよりずっと簡単でメンテナンス性が向上します。
次のデモを確認してください。CSS変数とJavaScriptだけでインタラクティブにサイドバーを切り替えたり、blend modeプロパティや背景色を変更したりできます。
CSS変数へのブラウザー対応状況
執筆時点で、IE11(非対応)とMicrosoft Edge(対応しているがバグが多発)を除き、主要ブラウザーはCSS変数に対応しています。
対応していないブラウザーを考慮に入れるなら、ダミー条件クエリで@supportsブロックを使いコードを書く方法があります。
section {
color: gray;
}
@supports(--css: variables) {
section {
--my-color: blue;
color: var(--my-color, 'blue');
}
}
IEやEdgeが@supportsに対応していればこれでOKです。さらに関数var()内のフォールバック値を使えば安全性が高まり、低機能ブラウザーにも柔軟に対応できます。
ChromeをはじめとするCSS変数対応ブラウザーでは、<section>要素内のテキストは青になります。
IE11はCSS変数に対応していないので、テキストはグレーです。
デモを確認してください。
CSS変数を使っても、非対応ブラウザーが主流のプロジェクトでは、コードが複雑になり、メンテナンスがちょっとした悪夢になるデメリットがあります。
その場合は、PostCSSでcssnextで、CSSコードを非対応ブラウザー用に変換します(JavaScriptのトランスパイラーのようなイメージです)。
参考資料
ブラウザー対応に関する回避策やおもしろい用途を、CSS変数全般を詳しく知りたい人は、以下の資料を参考にしてください。
- CSS変数のためのCSSカスタムプロパティ:Module Level 1:W3C仕様
- CSS変数の使い方 — MDN
- CSSConf Asia 2016でのCSS変数に関するLea Verouの講演 (動画)
- CSS変数とプリプロセッサー変数の違い:Chris Coyier (CSS-Tricks)
- いまこそCSSカスタムプロパティを使い始めよう:Serg Hospodarets (Smashing Magazine)
- CSS変数スコープのローカル化:なに、なぜ、どのように?:Una Kravets
- カスタムプロパティを使ってプラグマティックで実用的、プログレッシブなテーマを:Harry Roberts (CSS Wizardry)
- CSS変数を使ったカスタマイズ可能なSVGアイコン:Amelia Bellamy-Royds (CodePen).
おもしろいデモ
- CSS変数でアニメーション:Wes Bos
- JSでCSS変数を変更:Wes Bos
- CSS変数でシンプルなレスポンシブグリッド:Chris Coyier
- CSSカスタムプロパティを使ったSlackのテーマ:Stephanie
- Ana TudorのCodePenデモ「CSS variables」
(原文:A Practical Guide to CSS Variables (Custom Properties))
[翻訳:新岡祐佳子/編集:Livit]