CSS カスタムプロパティ(CSS 変数)についてちょっと調べたことがあったので、この場を借りてまとめておきます。
はじめに
CSS はスタイルのプロパティ値を設定できても参照ができない。
たとえばボックスの height
をテキストの行数で指定したくても、現在の line-height
を参照することができないので calc()
などで計算することはできず、あらかじめ算出した固定値を直接設定する必要がある。
CSS カスタムプロパティを使えば、意味付けされた値(変数)を定義し、複数のスタイルプロパティから参照したり、calc()
での計算に用いることが可能になる。
.line-clump {
/* 1行の高さ */
--line-height: 1.5em; /* 単位付きのプロパティ値 */
/* 表示行数 */
--line-count: 3; /* 整数値 */
/*リーダ用文字列を指定 */
--leader: "…"; /* 文字列 (::before や ::after から参照できる) */
line-height: var(--line-height); /* 参照にはvar()を使う */
/* ブロックの高さ(行高 x 行数) */
height: calc(var(--line-count) * var(--line-height)); /* calcでの計算に使える*/
width: 30em;
overflow: hidden;
}
- 【デモ】 三点リーダ
See the Pen OOdrWb by kumazo (@kumazo) on CodePen.
この値をスタイルシートの外部から与えたり動的に変更したい。
本稿では、CSS カスタムプロパティ(CSS 変数)を要素スタイルのパラメータ的に使えないものか、プログラマ視点で掘り下げる。
サンプルの動作は Chrome (63)で確認した。
CSS カスタムプロパティの仕様の確認というより、ほとんど Chrome 実装の動作検証になっているレベルなので、ほかのブラウザではどうなるのか分からない。
個別要素へのカスタムプロパティ注入
CSS カスタムプロパティは「CSS変数」とも呼ばれるが、プログラマ的には「変数」というより「オーバーライド可能な定数」と言ったほうがしっくりくる。
カスタムプロパティも、通常のスタイルプロパティと同様に、要素階層をカスケードする。
従って、ある要素で定義したカスタムプロパティは子要素と弟要素から参照することがきる。
#hoge {
--color: red
}
/* 子要素 */
#hoge span {
color: var(--color);
}
/* 弟要素 */
#hoge ~ p {
color: var(--color);
}
さらに、下位要素で同じカスタムプロパティ名を定義することで値を上書き(オーバーライド)することができる。
要素の style 属性で直接カスタムプロパティを定義すれば、要素への CSS パラメータ を与えているかのように使うことができる。
#color-circle.plate {
position: relative;
width: calc(2 * var(--r));
height: calc(2 * var(--r));
display: flex;
justify-content: center;
}
#color-circle.plate > .item {
position: absolute;
width: 60px;
height: 60px;
--angle: calc(calc(var(--i) * 360) / var(--n));
background-color: hsl(var(--angle), 100%, 50%);
transform-origin: 50% var(--r);
transform: rotate(calc(var(--angle) * 1deg));
}
<div id="color-circle" class="plate" style="--r:200px; --n:12">
<div class="item" style="--i:1"></div>
<div class="item" style="--i:2"></div>
<div class="item" style="--i:3"></div>
<div class="item" style="--i:4"></div>
<div class="item" style="--i:5"></div>
<div class="item" style="--i:6"></div>
<div class="item" style="--i:7"></div>
<div class="item" style="--i:8"></div>
<div class="item" style="--i:9"></div>
<div class="item" style="--i:10"></div>
<div class="item" style="--i:11"></div>
<div class="item" style="--i:12"></div>
</div>
- 色相環(style属性) - CodePen
style 属性でインデックスを指定するのはちょっとダサい。
できれば要素数や位置インデックスから値を算出できたいが、CSS だけではできない。
せめて data 属性を使いたいが、attr()
も役に立たない。
もちろん、JavaScript を使えばどうにでもなる。
// 要素数からパラメータ値を算出し、カスタムパラメータとして設定する
var num = $("#color-circle > .item").length;
var radius = $("#color-circle").width() / 2;
$("#color-circle").css({"--r":radius+"px", "--n":num+""});
$("#color-circle > .item").each(function(i){
$(this).css({"--i":i+""});
});
- 色相環(要素数から算出) -- CodePen
var num = 24;
var radius = 320;
$("#color-circle").css({"--r":radius+"px", "--n":num+""});
for (var i=0; i<num; i++) {
// 要素を追加してしまう
$("#color-circle").append($("<div>")
.addClass("item")
.css({"--i":i+""}));
}
<div id="color-circle" class="plate"><!--空 --></div>
- 色相環(動的生成) - CodePen
環境に依存したカスタムプロパティの切り替え
基本となるスタイルシートからカスタムプロパティだけを@importなどで外出しにすれば、それを設定ファイルのようにして、スタイルやテーマを環境によって制御するようなこともできる。
:root {
--debug-box: block;
}
/* 設定 css の読み込み */
@import "config.css";
/* デバックモードで可視となるクラス */
.debug-box {
display: var(--debug-box, none);
color: red;
border: 2px dotted red;
}
- デバッグモード表示 - CodePen
言語やメディアタイプでスタイルを切り替えるのも何かに使えるかもしれない。
/* langによって表示内容を切り替える */
nav:lang(ja) {
--back: "戻る";
--next: "次へ";
}
nav:lang(en) {
--back: "Back";
--next: "Next";
}
nav .back::after {
content: var(--back);
}
nav .next::after {
content: var(--next);
}
<nav lang="ja">
<a class="back" href="#"></a> <a class="next" href="#"></a>
</nav>
- 言語環境による文言の差し替え - CodePen
/* スクリーンではtitle属性の内容を表示させる */
@media screen {
:root {
--link-assist: title;
}
}
/* 印刷ではhref属性のURLを表示させる */
@media print {
:root {
--link-assist: href;
}
}
#comment a::after {
content: "(" attr(var(--link-assist)) ")";
}
<div id="comment">
<p>『好きなサイトは<a href="https://qiita.com" title="Qiita" target="_blank">ここ</a>です!』</p>
</div>
- メディアタイプに依存したリンク表示の差し替え - CodePen
ユーザアクションによる動的スタイル変更
フォームコントロール
状態を持つ要素でカスタムプロパティを定義すると、スタイルを動的に変更できる。
ただ、CSS は親要素をセレクトできないので、カスタムプロパティ値を使える状況が限られる。
チェックボックスやラジオボタンは親要素をもたないので :checked セレクタで弟要素にプロパティをカスケードすることができる。
<div id="rgb01">
<input type="checkbox" id="r"><label for="r">Red</label>
<input type="checkbox" id="g"><label for="g">Green</label>
<input type="checkbox" id="b"><label for="b">Blue</label>
<div class="rgb"></div>
</div>
#r:checked ~ .rgb {--r: 255}
#g:checked ~ .rgb {--g: 255}
#b:checked ~ .rgb {--b: 255}
.rgb {
background-color: rgb(var(--r, 0), var(--g, 0), var(--b, 0));
}
- 【デモ】 RGB混色
チェックボックスの :checked セレクタでカスタムプロパティ値を組み合わせて、RGB値を動的に変更する。
対象の要素はチェックボックスの兄弟要素でなければならない。
See the Pen qVewpX by kumazo (@kumazo) on CodePen.
しかし SELECT の OPTION ではそうはいかない。OPITON 要素は SELECT 要素を必ず親に持つので、カスタムプロパティを持たせてもその外の要素から参照できない。
このような場合 JavaScript を使わざるを得ない。
フォント:
<select id="font-family" onchange="changeFont(this)">
<option>serif</option>
<option>sans-serif</option>
<option>cursive</option>
<option>fantasy</option>
<option>monospace</option>
</select>
フォントサイズ:
<select id="font-size" onchange="changeFont(this)">
<option>10pt</option>
<option>12pt</option>
<option selected>16pt</option>
<option>20pt</option>
<option>24pt</option>
</select>
function changeFont(sel) {
/* カスタムプロパティを設定するにはsetProperty()を使う */
document.getElementById("font-sample")
.style.setProperty('--' + sel.id, sel.value);
}
#font-sample p {
font-family: var(--font-family, serif);
font-size: var(--font-size, 16pt);
}
- 【デモ】 フォントの変更
See the Pen RjXOoM by kumazo (@kumazo) on CodePen.
CSS アニメーション
カスタムプロパティの値を動的に変更できれば、animation
を制御するのに使える。
animation
の設定を変更するほか、 @keyframes
ごと差し替えたり、要素別のカスタムプロパティを @keyframes
内で参照することができる。
/* ラジオボタンやチェックボックスで変数の値を切り替える */
・・・
#normal:checked ~ .box {--direction : normal}
#reverse:checked ~ .box {--direction : reverse}
#alternate:checked ~ .box {--direction : alternate}
#alternate-reverse:checked ~ .box {--direction : alternate-reverse}
・・・
#running:checked ~ .box {--play-state : running}
#paused:checked ~ .box {--play-state : paused}
・・・
@keyframes act {
0% {
background: hsl(0, 100%, 50%);
}
100% {
/* 要素スタイルで定義したカスタムプロパティを参照する */
background: var(--color);
transform : var(--transform);
}
}
.box {
・・・
/* animation の設定がカスタムプロパティの値の変更によって動的に反映されsされる */
animation-name : var(--name, act);
animation-duration : var(--duration, 0s);
animation-timing-function : var(--timing-function, ease);
animation-delay : var(--delay, 0s);
animation-iteration-count : var(--iteration-count, 1);
animation-direction : var(--direction, normal);
animation-fill-mode : var(--fill-mode, none);
animation-play-state : var(--play-state, running);
}
/* 要素ごとにパラメータを設定 */
#box01 {
--color : hsl(240, 100%, 50%);
--transform : rotate(360deg);
}
・・・
- 【デモ】CSS animation の動作確認 - CodePen
@keyframes a01 {
0% {transform: translate(5px)}
50% {transform: translate(-5px)}
100% {transform: translate(5px)}
}
@keyframes a02 {
0% {transform: scale(1.05)}
50% {transform: scale(0.95)}
100% {transform: scale(1.05)}
}
・・・
#a01:active ~ .box {--name: a01}
#a02:active ~ .box {--name: a02}
・・・
#panel .box {
・・・
animation: var(--name, none) 100ms linear infinite;
}
- 【デモ】 ハコをふるわせる
CSS カスタムプロパティで animation の@keyframes
を切り替える
See the Pen VywZqR by kumazo (@kumazo) on CodePen.
逆に、@keyframes
内でカスタムプロパティを定義して要素のプロパティをフレームごとに切り替えることもできる。
@keyframes inc {
0% {--i: 0}
10% {--i: 1}
20% {--i: 2}
30% {--i: 3}
40% {--i: 4}
50% {--i: 5}
60% {--i: 6}
70% {--i: 7}
80% {--i: 8}
90% {--i: 9}
100% {--i:10}
}
.runnable {
animation : inc 10s infinite linear both;
}
#charge::before {
・・・
width : calc(var(--i) * 10%);
・・・
}
- 【デモ】@keyframes カウンタ
See the Pen PEVmpY by kumazo (@kumazo) on CodePen.
JavaScript
JavaScript からカスタムプロパティに値を設定することで、CSS パラメータとして要素スタイルを動的に制御すすことができる。
$(function(){
$(".ripple").on('mousedown', function(e) {
var x = e.offsetX;
var y = e.offsetY;
var r = $(this).width();
$(this).css({
"--x": x + "px",
"--y": y + "px",
"--r": r + "px"
});
});
});
- 【デモ】 マテリアル リップル
Chrome では JavaScript の mousedown イベントで設定したカスタムプロパティが CSS の:active
擬似クラスで拾えているので、マウス操作で要素スタイルを制御できる。
See the Pen KZJVjz by kumazo (@kumazo) on CodePen.
代替スタイルシート
「代替スタイルシート」というのは聞きなれないかもしれないが、ブラウザ側でスタイルシートを切り替えられるようにするための HTML 仕様で、今はほとんど忘れ去られている。
基本スタイルシートで参照するカスタムプロパティを代替スタイルシートに定義すれば、ユーザがメニューからデザインテーマを変更することができる。
代替スタイルシートの構成は LINK か STYLE のtitle 属性で行う。
<!-- 先頭のSTYLEがでおフォルト -->
<style title="デフォルト">
:root {
--fg-color: black;
--bg-color: white;
--accent-color: red;
}
</style>
<style title="カラフル">
:root {
--fg-color: blue;
--bg-color: ivory;
--accent-color: green;
}
</style>
- 代替スタイルシート - CodePen
ただし残念ながら、Chrome は代替スタイルシートをサポートしない。
Firefox なら今でもサポートしているので、インストールしている人は試してみてほしい。
その他気付き
-
url()
関数の引数ではvar()
が使えない。 - カスタムプロパティの定義で、同名のカスタムプロパティは使用できない。これが変数とは違うところだ。
#hoge {
--color: var(--color, red); /* エラー */
}
- デバッグが難しい。
Chrome の Developer Tools でカスタムプロパティの変数の値は展開されないので、calc() などが挟まると実際の最終的な値がわからない。
文字列と数値ならかろうじてcontent
で表示させることができるが、それも苦しい。
#hoge01 {
--string: "test";
--int: 123;
}
/* 文字列 */
#hoge01::after {
content: var(--string);
}
/* 数値 */
#hoge01::after {
counter-reset: c var(--int);
content: counter(c);
}
デモ
- 【デモ】アナログ時計デザイン
新旧の2つのアナログ時計のスタイルの定義は共通で、デザインだけカスタムプロパティで変更している
See the Pen mpvXoy by kumazo (@kumazo) on CodePen.
- 【デモ】 ローディングアニメ
カスタムプロパティで animation の初期状態やタイミングをずらす
See the Pen ZvEzJe by kumazo (@kumazo) on CodePen.
まとめ
間に合わなかったでござる。
参考
- CSS Custom Properties for Cascading Variables Module Level 1
https://www.w3.org/TR/css-variables/ - CSS カスケード変数のためのカスタムプロパティ — CSS Custom Properties for Cascading Variables Module Level 1 (日本語訳)
https://triple-underscore.github.io/css-variables-ja.html - CSSの変数を使う - CSS | MDN
(https://developer.mozilla.org/ja/docs/Web/CSS/Using_CSS_variables) - CSS Variables(カスタムプロパティ): なぜ、関心を持つべきか? | プログラミング | POSTD
http://postd.cc/css-variables-why-should-you-care/ - hsl()とcalc()、そしてCustom Propertiesを使った色の生成 - Hail2u
https://hail2u.net/blog/generating-colors-with-hsl-calc-and-custom-properties.html - Making Custom Properties (CSS Variables) More Dynamic | CSS-Tricks
https://css-tricks.com/making-custom-properties-css-variables-dynamic/ - そのほか CSS-Tricks 内のカスタムプロパティ記事
https://css-tricks.com/?s=custom+properties - CSS Variables(カスタムプロパティ)でCSSがより便利に! - Qiita
https://qiita.com/kyota/items/bd5d291809415cc2d7b1 - CSS Variables(カスタムプロパティ) は CSS Animation と使うと幸せ - Qiita
https://qiita.com/ygkn/items/33abcc3a5621bdf77c8d - CSS カスタムプロパティと JavaScript Event - Qiita
https://qiita.com/TSHiYK/items/ce72062235e48f3bdef8 - [jQuery]でcssプロパティを動的に追加、変更、削除する - Qiita
https://qiita.com/kazTera/items/ab5dd9fb5b2579b25c4d - CSSアニメーション 入門 - Qiita
https://qiita.com/soarflat/items/4a302e0cafa21477707f