1. はじめに
このセクションは規範的ではありません。
大規模な文書やアプリケーション (小規模なものでも) には、多くのCSSが含まれる場合があります。 CSSファイル内の値の多くは重複したデータになります。 例えば、 サイトがカラースキームを設定し、 サイト全体で3~4色を使い回すことがあります。 このデータを変更するのは困難で間違いが発生しやすく、 CSSファイル全体(場合によっては複数ファイル)に散在しているため、 置換操作(Find-and-Replace)も容易ではありません。
このモジュールでは、著者が任意の値を任意の名前でプロパティに割り当てられる カスタムプロパティ という著者定義のプロパティ群と、 それらの値を文書内の他のプロパティで利用できる var() 関数を導入します。 これにより、大きなファイルの可読性が向上し、 一見意味のない値にも分かりやすい名前が付きます。 編集作業も簡単で間違いが減り、 値を一箇所だけ変更すれば カスタムプロパティ を使ったすべての箇所に自動的に変更が伝播します。
1.1. 値の定義
この仕様は、[CSS2]の CSSプロパティ定義規約 と、[CSS-VALUES-3]の 値定義構文に従います。 この仕様で定義されていない値型はCSS Values & Units [CSS-VALUES-3]で定義されています。 他のCSSモジュールとの組み合わせで、これらの値型の定義が拡張されることもあります。
各プロパティの定義に記載された固有の値に加えて、 この仕様で定義されるすべてのプロパティは CSS全体キーワード もプロパティ値として受け入れます。 可読性のため、明示的に繰り返してはいません。
2. カスタムプロパティの定義: --* プロパティ群
この仕様は、 カスタムプロパティ と呼ばれる制約のないプロパティ群を定義します。 これらはvar()の代入値 を定義するためなどにも使われます。
Name: | --* |
---|---|
Value: | <declaration-value>? |
Initial: | 保証された無効値 |
Applies to: | すべての要素とすべての疑似要素(プロパティリストが制限されているものも含む) |
Inherited: | はい |
Percentages: | 該当なし |
Computed value: | 変数を置換した指定値、または保証された無効値 |
Canonical order: | 文法による |
Animation type: | 離散的 |
ユーザーエージェントは、このプロパティを非視覚メディアを含むすべてのメディアでサポートすることが期待されています。
カスタムプロパティとは、 名前が2つのハイフン(U+002D HYPHEN-MINUS)で始まるプロパティのことです。 例えば --foo のようなものです。 <custom-property-name> 生成規則はこれに対応し、 2つのハイフンで始まる任意の<dashed-ident>(2つのハイフンで始まる有効な識別子)と定義されます。 ただし -- 自体は除外されており、 これはCSSで将来利用されるために予約されています。 カスタムプロパティは著者および利用者専用であり、 CSSがここで示す以上の意味を与えることはありません。
テスト
- variable-declaration-29.html (ライブテスト) (ソース)
- variable-declaration-31.html (ライブテスト) (ソース)
- variable-declaration-32.html (ライブテスト) (ソース)
- variable-declaration-33.html (ライブテスト) (ソース)
- variable-declaration-34.html (ライブテスト) (ソース)
- variable-declaration-35.html (ライブテスト) (ソース)
- variable-declaration-36.html (ライブテスト) (ソース)
- variable-declaration-40.html (ライブテスト) (ソース)
- variable-declaration-41.html (ライブテスト) (ソース)
- variable-declaration-42.html (ライブテスト) (ソース)
- variable-empty-name-reserved.html (ライブテスト) (ソース)
:root{ --main-color : #06c; --accent-color : #006; } /* CSSファイルの残り */ #foo h1{ color : var ( --main-color); }
名前付けにより色の記憶法となり、 色コードの見落としやすいタイプミスを防ぎます。 また、テーマカラーを変更する場合も (カスタムプロパティ値のみ変更すればよいので) 多数のスタイルシートを編集する必要がなくなります。
他のCSSプロパティとは異なり、 カスタムプロパティ名はASCII大文字・小文字を区別しないものではありません。 代わりに、カスタムプロパティ名は 一致する場合のみ同一とみなします。
さらに、--foó と --foó も異なるプロパティです。 前者はU+00F3(ラテン小文字oアキュート付き)で綴られ、 後者はASCIIの"o"+U+0301(合成アキュートアクセント)で綴られています。 "一致する"関係は、文字列をコードポイントごとに直接比較して判断します。 これによりUnicode正規化やロケール特有の照合の複雑さや落とし穴を避けます。
OSやキーボード、入力方法によって、 見た目が同じテキストでも異なるコードポイント列が使われる場合があります。 著者は混乱を避けるため、 変数名にわかりやすい名前を使うか、エスケープなどの手法で 見た目が似ているコード列が一致するようにしましょう。 例については [CHARMOD-NORM] の2.3節を参照してください。
--fijord : red; --fijord : green; --fijord : blue; .test{ background-color : var ( --fijord); }
理由は、最初のカスタムプロパティは LATIN SMALL LETTER F + LATIN SMALL LETTER I + LATIN SMALL LETTER J の文字列を使い、 2番目は見た目は同じですが LATIN SMALL LETTER F + LATIN SMALL LIGATURE IJ、 3番目はLATIN SMALL LIGATURE FI + LATIN SMALL LETTER Jの文字列を使っているためです。
つまり、CSSには見た目が似ていても3つの異なるカスタムプロパティがあり、 そのうち2つは未使用です。
カスタムプロパティは、 all プロパティでリセットされません。 将来的にすべての変数をリセットするプロパティを定義するかもしれません。
CSS全体キーワードはカスタムプロパティでも利用でき、 他のプロパティと同じ意味を持ちます。
テスト
- variable-declaration-43.html (ライブテスト) (ソース)
- variable-declaration-44.html (ライブテスト) (ソース)
- variable-declaration-45.html (ライブテスト) (ソース)
- variable-declaration-46.html (ライブテスト) (ソース)
- variable-declaration-47.html (ライブテスト) (ソース)
- variable-declaration-56.html (ライブテスト) (ソース)
- variable-declaration-57.html (ライブテスト) (ソース)
- variable-declaration-58.html (ライブテスト) (ソース)
- variable-declaration-60.html (ライブテスト) (ソース)
- variable-definition-keywords.html (ライブテスト) (ソース)
注: つまり、カスケード値の算定時に通常通り解釈され、 カスタムプロパティの値としては保持されず、 対応する変数による置換には使われません。
注: このモジュールは カスタムプロパティと var() 関数による 「変数」利用に焦点を当てていますが、 実際のカスタムプロパティとしてスクリプトで解析・利用することもできます。 CSS Extensions仕様 [CSS-EXTENSIONS] で、これらの用途がさらに拡張される見込みです。
カスタムプロパティは通常のプロパティなので、
どの要素にも宣言でき、
通常の継承やカスケード規則で解決されます。
@media
や他の条件付き規則で条件付けもでき、
HTMLの style
属性でも使えますし、
CSSOMで読み書きも可能です。
テスト
- css-vars-custom-property-inheritance.html (ライブテスト) (ソース)
- variable-created-document.html (ライブテスト) (ソース)
- variable-created-element.html (ライブテスト) (ソース)
- variable-cssText.html (live test) (ソース)
- variable-declaration-06.html (ライブテスト) (ソース)
- variable-definition-cascading.html (ライブテスト) (ソース)
- variable-external-declaration-01.html (ライブテスト) (ソース)
- variable-external-reference-01.html (ライブテスト) (ソース)
- variable-external-supports-01.html (ライブテスト) (ソース)
- variable-first-letter.html (ライブテスト) (ソース)
- variable-first-line.html (ライブテスト) (ソース)
- variable-pseudo-element.html (ライブテスト) (ソース)
- variable-reference-13.html (ライブテスト) (ソース)
- variable-reference-14.html (ライブテスト) (ソース)
- variable-reference-shorthands.html (ライブテスト) (ソース)
- variable-reference-visited.html (ライブテスト) (ソース)
特に、トランジションやアニメーションも可能ですが、 UAが内容を解釈できないため、 「50%で切り替え」挙動(賢く補間できない値ペアの場合の挙動)になります。 ただし、カスタムプロパティを @keyframes ルールで使うと アニメーション汚染となり、 アニメーションプロパティで var() 関数を通じて参照された場合の扱いに影響します。
テスト
- variable-animation-from-to.html (ライブテスト) (ソース)
- variable-animation-over-transition.html (ライブテスト) (ソース)
- variable-animation-substitute-into-keyframe-shorthand.html (ライブテスト) (ソース)
- variable-animation-substitute-into-keyframe-transform.html (ライブテスト) (ソース)
- variable-animation-substitute-into-keyframe.html (ライブテスト) (ソース)
- variable-animation-substitute-within-keyframe-fallback.html (ライブテスト) (ソース)
- variable-animation-substitute-within-keyframe-multiple.html (ライブテスト) (ソース)
- variable-animation-substitute-within-keyframe.html (ライブテスト) (ソース)
- variable-animation-to-only.html (ライブテスト) (ソース)
アニメーション汚染は 「伝染性」です。 アニメーション汚染プロパティを参照するカスタムプロパティも アニメーション汚染となります。
:root{ --header-color : #06c; }
ルート要素にカスタムプロパティ --header-color を宣言し、その値を"#06c"に設定しています。 このプロパティは文書の残りの要素にも継承されます。 var() 関数で値を参照できます:
h1{ background-color : var ( --header-color); }
この規則は background-color: #06c; と同じですが、変数名により色の由来が明確になり、 文書内でvar(--header-color)を他の要素にも使えば、 ルート要素の--header-colorプロパティを変更するだけで すべての利用箇所を一度に更新できます。
:root{ --color : blue; } div{ --color : green; } #alert{ --color : red; } *{ color : var ( --color); } <p>私はルート要素から青を継承しました!</p> <div>私は直接緑を設定されました!</div> <div id='alert' > 私は直接赤を設定されました! <p>私も赤です, 継承によるものです!</p> </div>
:root, :root:lang ( en) { --external-link : "external link" ;} :root:lang ( el) { --external-link : "εξωτερικός σύνδεσμος" ;} a[ href^="http" ] ::after{ content : " (" var ( --external-link) ")" }
変数宣言は別ファイルに分離することもでき、 翻訳の保守がより簡単になります。
2.1. カスタムプロパティ値の構文
カスタムプロパティの許容される構文は非常に寛容です。 <declaration-value> 生成規則は 1つ以上のトークンからなる任意の並びにマッチしますが、並び内に <bad-string-token>、 <bad-url-token>、 対応しない <)-token>、 <]-token>、 <}-token>、 またはトップレベルの <semicolon-token> トークンや 値が "!" である <delim-token> トークンが含まれていない限り有効です。
テスト
さらに、カスタムプロパティの値にvar()参照が含まれる場合、 var()参照は指定された var()文法に従い有効でなければなりません。 そうでない場合、カスタムプロパティは無効となり無視されます。
注: この定義と一般的なCSS構文規則により、 カスタムプロパティ値には対応しない引用符や括弧が含まれることはなく、 再シリアライズ時に囲むスタイル規則など大きな構文構造に影響を与えることはありません。
注: カスタムプロパティには末尾に !important を含めることができますが、これはCSSパーサーによって自動的に値から除去され、 カスケード内でそのカスタムプロパティが「重要」となります。 つまり、トップレベルの "!" 文字の禁止は !important の使用を妨げません。 !important は構文チェックの前に除去されます。
--foo : if ( x >5 ) this.width =10 ;
この値は変数としては明らかに役立ちませんが、 通常のプロパティでは無効ですが、 JavaScriptで読み取って処理することは可能です。
カスタムプロパティの値、 およびカスタムプロパティに代入されるvar()関数の値は、 大文字・小文字を区別し、 著者が指定した元の大文字・小文字を保持しなければなりません。 (多くのCSS値はASCII大文字・小文字を区別しないため、 ユーザーエージェントはそれらを「正規化」して単一の大文字・小文字にできますが、 カスタムプロパティではそれは許可されません。)
これには副次的な影響があります。
例えば、CSS内の相対URLは、
値が現れるスタイルシートのベースURLから解決されます。
ただし、--my-image:
url(foo.jpg); のようなカスタムプロパティが
スタイルシートに現れた場合、
すぐに絶対URLに解決されることはなく、
その変数が別の
スタイルシートで
background: var(--my-image); のように使われた場合、
その時点で
に解決されます。
2.2. 保証された無効値
カスタムプロパティの初期値は 保証された無効値です。 § 3 カスケーディング変数の利用: var()記法で定義されているように、 var()で この値を持つカスタムプロパティを代入すると、 それを参照するプロパティは算出値時に無効となります。
この値は空文字列としてシリアライズされますが、 実際に --foo: ; のように カスタムプロパティに空値を書き込むことは 有効な(空の)値であり、 保証された無効値ではありません。 何らかの理由で変数を手動で 保証された無効値にリセットしたい場合は、 キーワード initial を使うことで可能です。
2.3. 依存サイクルの解決
カスタムプロパティは ほぼ完全に評価されずに残されますが、 値の中に var() 関数のみ許可・評価されます。 これにより、カスタムプロパティが自身を参照する var() を使ったり、 2つ以上のカスタムプロパティが互いに参照し合うことで 循環依存が発生することがあります。
各要素ごとに、 各カスタムプロパティをノードとする 有向依存グラフを作成します。 カスタムプロパティ prop の値に var() 関数が var プロパティを参照している場合(var() の フォールバック引数内も含む)、prop から var へのエッジを追加します。 カスタムプロパティから自身へのエッジも可能です。
依存グラフにサイクルがある場合、 サイクル内のすべてのカスタムプロパティは 算出値時に無効となります。
テスト
注: 依存サイクルに関与する定義済みプロパティは、 値内に無効な変数が含まれるようになる(算出値時に無効になる)か、 独自のサイクル処理を定義する(例えば font-size が em 値を使う場合など)。 カスタムプロパティのように 保証された無効値にはなりません。
:root{ --main-color : #c06; --accent-background : linear-gradient ( to top, var ( --main-color), white); }
--accent-backgroundプロパティ (var(--main-color) を使う他のプロパティも含む)は --main-colorプロパティが変更されると自動的に更新されます。
:root{ --one : calc ( var ( --two) +20 px ); --two : calc ( var ( --one) -20 px ); }
重要なのは、カスタムプロパティは値内のvar()関数を算出値時に解決するということです。 これは値が継承される前に発生します。 一般的に、循環依存は同じ要素上の複数のカスタムプロパティが互いを参照する場合のみ発生し、 要素ツリー上位で定義されたカスタムプロパティが下位の要素で定義されたプロパティと 循環参照を起こすことはありません。
< one >< two >< three /></ two ></ one > < style > one { --foo : 10 px ; } two { --bar : calc( var ( --foo ) + 10 px ); } three { --foo : calc( var ( --bar ) + 10 px ); } </ style >
<one> 要素は --foo の値を定義します。 <two> 要素はこの値を継承し、さらに --bar に foo 変数を使って値を割り当てます。 最後に <three> 要素は、変数代入後の --bar の値を継承し (つまり calc(10px + 10px) という値になります)、 その値を使って --foo を再定義します。 継承された --bar の値には <one> で定義された --foo プロパティへの参照は もはや含まれていないため、 var(--bar) 変数で --foo を定義しても循環せず、 実際には通常のプロパティで変数として参照されると 30px に解決されます。
3. カスケーディング変数の利用: var() 記法
カスタムプロパティの値は、 var()関数を使って 他のプロパティ値に代入することができます。 var()の構文は以下の通りです:
var () =var ( <custom-property-name>, <declaration-value>?)
テスト
- variable-reference-07.html (ライブテスト) (ソース)
- variable-reference-08.html (ライブテスト) (ソース)
- variable-reference-09.html (ライブテスト) (ソース)
- variable-reference-10.html (ライブテスト) (ソース)
- variable-reference-17.html (ライブテスト) (ソース)
- variable-reference-20.html (ライブテスト) (ソース)
- variable-reference-21.html (ライブテスト) (ソース)
- variable-reference-22.html (ライブテスト) (ソース)
- variable-reference-23.html (ライブテスト) (ソース)
- variable-reference-24.html (ライブテスト) (ソース)
- variable-reference-25.html (ライブテスト) (ソース)
- variable-reference-28.html (ライブテスト) (ソース)
- variable-reference-29.html (ライブテスト) (ソース)
- variable-reference-31.html (ライブテスト) (ソース)
- variable-reference-32.html (ライブテスト) (ソース)
- variable-reference-33.html (ライブテスト) (ソース)
- variable-reference-34.html (ライブテスト) (ソース)
- variable-reference-35.html (ライブテスト) (ソース)
- variable-reference.html (ライブテスト) (ソース)
@supports
- variable-supports-01.html (ライブテスト) (ソース)
- variable-supports-02.html (ライブテスト) (ソース)
- variable-supports-03.html (ライブテスト) (ソース)
- variable-supports-04.html (ライブテスト) (ソース)
- variable-supports-05.html (ライブテスト) (ソース)
- variable-supports-06.html (ライブテスト) (ソース)
- variable-supports-07.html (ライブテスト) (ソース)
- variable-supports-08.html (ライブテスト) (ソース)
- variable-supports-09.html (ライブテスト) (ソース)
- variable-supports-10.html (ライブテスト) (ソース)
- variable-supports-11.html (ライブテスト) (ソース)
- variable-supports-12.html (ライブテスト) (ソース)
- variable-supports-13.html (ライブテスト) (ソース)
- variable-supports-14.html (ライブテスト) (ソース)
- variable-supports-15.html (ライブテスト) (ソース)
- variable-supports-16.html (ライブテスト) (ソース)
- variable-supports-17.html (ライブテスト) (ソース)
- variable-supports-18.html (ライブテスト) (ソース)
- variable-supports-19.html (ライブテスト) (ソース)
- variable-supports-20.html (ライブテスト) (ソース)
- variable-supports-21.html (ライブテスト) (ソース)
- variable-supports-22.html (ライブテスト) (ソース)
- variable-supports-23.html (ライブテスト) (ソース)
- variable-supports-24.html (ライブテスト) (ソース)
- variable-supports-25.html (ライブテスト) (ソース)
- variable-supports-26.html (ライブテスト) (ソース)
- variable-supports-27.html (ライブテスト) (ソース)
- variable-supports-28.html (ライブテスト) (ソース)
- variable-supports-29.html (ライブテスト) (ソース)
- variable-supports-30.html (ライブテスト) (ソース)
- variable-supports-31.html (ライブテスト) (ソース)
- variable-supports-32.html (ライブテスト) (ソース)
- variable-supports-33.html (ライブテスト) (ソース)
- variable-supports-34.html (ライブテスト) (ソース)
- variable-supports-35.html (ライブテスト) (ソース)
- variable-supports-36.html (ライブテスト) (ソース)
- variable-supports-37.html (ライブテスト) (ソース)
- variable-supports-38.html (ライブテスト) (ソース)
- variable-supports-39.html (ライブテスト) (ソース)
- variable-supports-40.html (ライブテスト) (ソース)
- variable-supports-41.html (ライブテスト) (ソース)
- variable-supports-42.html (ライブテスト) (ソース)
- variable-supports-43.html (ライブテスト) (ソース)
- variable-supports-44.html (ライブテスト) (ソース)
- variable-supports-45.html (ライブテスト) (ソース)
- variable-supports-46.html (ライブテスト) (ソース)
- variable-supports-47.html (ライブテスト) (ソース)
- variable-supports-48.html (ライブテスト) (ソース)
- variable-supports-49.html (ライブテスト) (ソース)
- variable-supports-50.html (ライブテスト) (ソース)
- variable-supports-51.html (ライブテスト) (ソース)
- variable-supports-52.html (ライブテスト) (ソース)
- variable-supports-53.html (ライブテスト) (ソース)
- variable-supports-54.html (ライブテスト) (ソース)
- variable-supports-55.html (ライブテスト) (ソース)
- variable-supports-56.html (ライブテスト) (ソース)
- variable-supports-57.html (ライブテスト) (ソース)
- variable-supports-58.html (ライブテスト) (ソース)
- variable-supports-59.html (ライブテスト) (ソース)
- variable-supports-60.html (ライブテスト) (ソース)
- variable-supports-61.html (ライブテスト) (ソース)
- variable-supports-62.html (ライブテスト) (ソース)
- variable-supports-63.html (ライブテスト) (ソース)
- variable-supports-64.html (ライブテスト) (ソース)
- variable-supports-65.html (ライブテスト) (ソース)
- variable-supports-66.html (ライブテスト) (ソース)
- variable-supports-67.html (ライブテスト) (ソース)
通常のカンマ省略規則(値を区切らない場合はカンマを省略する必要がある)への例外として、 何も後に続かない素のカンマは、var()内では有効として扱われなければならず、 空のフォールバック値を示します。
テスト
注: つまり、var(--a,) は有効な関数であり、 --a カスタムプロパティが無効または存在しない場合、 var() は何も代入しないことを指定します。
var() 関数は、 要素の任意のプロパティの値の任意の部分に使用できます。 var() 関数はプロパティ名やセレクター、 その他プロパティ値以外の用途には使用できません。 (そうすると通常は無効な構文になったり、変数と関係のない意味の値となります。)
テスト
.foo{ --side : margin-top; var ( --side) :20 px ; }
これは margin-top: 20px; を設定することと同じではありません。 実際には、2つ目の宣言はプロパティ名が無効なため、構文エラーとして単に破棄されます。
関数の第1引数は、代入対象となるカスタムプロパティの名前です。 第2引数が指定されていれば、それはフォールバック値であり、 参照されたカスタムプロパティの値が保証された無効値の場合に代入値として使われます。
テスト
- variable-declaration-08.html (ライブテスト) (ソース)
- variable-declaration-09.html (ライブテスト) (ソース)
- variable-declaration-10.html (ライブテスト) (ソース)
- variable-declaration-11.html (ライブテスト) (ソース)
- variable-declaration-12.html (ライブテスト) (ソース)
- variable-declaration-13.html (ライブテスト) (ソース)
- variable-declaration-22.html (ライブテスト) (ソース)
注: フォールバックの構文はカスタムプロパティ同様にカンマが許容されます。 例えば、var(--foo, red, blue)はフォールバック値としてred, blueを定義します。 つまり、最初のカンマから関数の終わりまでの部分がフォールバック値として扱われます。
フォールバックがない場合、 アプリの著者はコンポーネントが使用するすべての変数に値を指定する必要があります。 フォールバックがあれば、コンポーネントの著者がデフォルト値を指定できるので、 アプリの著者は上書きしたい変数だけ値を指定すれば済みます。
/* コンポーネントのスタイル: */ .component .header{ color : var ( --header-color, blue); } .component .text{ color : var ( --text-color, black); } /* 大規模アプリケーションのスタイル: */ .component{ --text-color : #080; /* header-colorは設定されていないため blue(フォールバック値)のまま */ }
プロパティに1つ以上のvar()関数が含まれていて、 それらの関数が構文的に有効である場合、 プロパティ全体の文法はパース時点で有効とみなされます。 実際の構文チェックは算出値時に、 var()関数が代入された後にのみ行われます。
テスト
プロパティ値内でvar()を代入するには:
- カスタムプロパティが var()関数の第1引数で指定され、 かつそのプロパティがアニメーション汚染であり、 var()関数がアニメーション不可なプロパティで使われている場合、 このアルゴリズムの残りの処理ではカスタムプロパティは初期値を持つものとして扱う。
- カスタムプロパティが var()関数の第1引数で指定され、 その値が初期値以外の場合、 var()関数を対応するカスタムプロパティの値で置き換える。
- それ以外の場合、var()関数が第2引数としてフォールバック値を持つ場合、 var()関数をフォールバック値で置き換える。 フォールバック内にvar()参照があれば、 それらも代入する。
-
それ以外の場合、
var()関数を含むプロパティは算出値時に無効となる。
注: 他にもプロパティが算出値時に無効になる要因があります。
テスト
- css-variable-change-style-001.html (ライブテスト) (ソース)
- css-variable-change-style-002.html (ライブテスト) (ソース)
- variable-declaration-01.html (ライブテスト) (ソース)
- variable-declaration-02.html (ライブテスト) (ソース)
- variable-declaration-03.html (ライブテスト) (ソース)
- variable-declaration-04.html (ライブテスト) (ソース)
- variable-declaration-05.html (ライブテスト) (ソース)
- variable-generated-content-dynamic-001.html (ライブテスト) (ソース)
- variable-presentation-attribute.html (ライブテスト) (ソース)
- variable-reference-01.html (ライブテスト) (ソース)
- variable-reference-02.html (ライブテスト) (ソース)
- variable-reference-03.html (ライブテスト) (ソース)
- variable-reference-04.html (ライブテスト) (ソース)
- variable-reference-05.html (ライブテスト) (ソース)
- variable-reference-12.html (ライブテスト) (ソース)
- variable-reference-16.html (ライブテスト) (ソース)
- variable-reference-40.html (ライブテスト) (ソース)
- variable-reference-refresh.html (ライブテスト) (ソース)
- variable-substitution-background-properties.html (ライブテスト) (ソース)
- variable-substitution-basic.html (ライブテスト) (ソース)
- variable-substitution-filters.html (ライブテスト) (ソース)
- variable-substitution-replaced-size.html (ライブテスト) (ソース)
- variable-substitution-shadow-properties.html (ライブテスト) (ソース)
- variable-substitution-variable-declaration.html (ライブテスト) (ソース)
CSSOM
.foo{ --gap : 20 ; margin-top : var ( --gap) px; }
これは margin-top: 20px;(長さ)を設定することと同じではありません。 実際には、margin-top: 20 px; (数値+識別子)と同じであり、 margin-top プロパティに対して単に無効な値です。 ただし、calc() を使えば、次のように同じことを正しく実現できます:
.foo{ --gap : 20 ; margin-top : calc ( var ( --gap) *1 px ); }
テスト
var() 関数は算出値時に 代入されます。 すべての var() 関数が代入された後に、 宣言が定義された文法と一致しない場合、 その宣言は算出値時に無効となります。
テスト
- variable-declaration-16.html (ライブテスト) (ソース)
- variable-declaration-17.html (ライブテスト) (ソース)
- variable-declaration-18.html (ライブテスト) (ソース)
- variable-declaration-19.html (ライブテスト) (ソース)
- variable-declaration-21.html (ライブテスト) (ソース)
- variable-transitions-transition-property-all-before-value.html (ライブテスト) (ソース)
- variable-transitions-value-before-transition-property-all.html (ライブテスト) (ソース)
宣言に含まれるすべてのvar()関数が代入された後、 その宣言がCSS全体キーワード(および空白のみ)で構成されている場合、 その値は最初からそのキーワードが指定値だったかのように決定されます。
:root{ --looks-valid : 20 px ; } p{ background-color : var ( --looks-valid); }
20pxはbackground-colorに対して無効な値なので、 このプロパティはtransparent(background-colorの初期値)になります。
プロパティがデフォルトで継承されるもの(例:color)だった場合は、 初期値ではなく継承値になります。
p{ color : var ( --does-not-exist, initial); }
上記コードでは、--does-not-existプロパティが存在しない、 または算出値時に無効の場合、 var()は initialキーワードを代入し、 プロパティは元々color: initialだったかのように振る舞います。 これにより、継承ではなくドキュメントの初期color値が使われます(フォールバックがなければ継承)。
3.1. 無効な変数
カスタムプロパティの値が 保証された無効値の場合、var()関数で代入することはできません。 これを試みると宣言は算出値時に無効となります(有効なフォールバックが指定されていない場合)。
宣言は、算出値時に無効になる場合があります。 それは、var()が カスタムプロパティ(上記のように 保証された無効値)を参照する場合や、 有効なカスタムプロパティを使っていても 変数代入後の値が無効な場合です。 この場合、プロパティの型によって算出値は以下のいずれかになります:
- プロパティが未登録のカスタムプロパティの場合
- 登録済みカスタムプロパティで汎用構文の場合
-
算出値は保証された無効値となります。
- それ以外の場合
-
プロパティが継承されるものなら継承値、そうでないなら初期値となり、 そのプロパティ値がunsetキーワードとして指定された場合と同様です。
:root{ --not-a-color : 20 px ; } p{ background-color : red; } p{ background-color : var ( --not-a-color); }
<p> 要素は透明な背景(background-colorの初期値)になります。 赤い背景にはなりません。 カスタムプロパティ自体が解除されている場合や、 無効なvar()関数を含む場合も同じです。
この挙動は、著者がスタイルシートに直接background-color: 20pxと書いた場合とは異なります。 その場合は通常の構文エラーとなり、規則が破棄され、 background-color: red規則が使われます。
注: 算出値時に無効という概念は、 変数は他の構文エラーのように「早期失敗」できないために存在します。 UAがプロパティ値が無効だと気づいた時点では、 すでに他のカスケード値は破棄されています。
3.2. ショートハンドプロパティ内の変数
var()関数は ショートハンドプロパティを構成するロングハンドに分解するときや、 その構成ロングハンドからショートハンドプロパティに再シリアライズするときに 一部の複雑さをもたらします。
ショートハンドプロパティの値にvar()関数が含まれる場合、 関連するロングハンドプロパティは、 そのショートハンドに変数が含まれていることを示す特別な(著者からは観測できない) 代入保留値で埋められる必要があります。 これは、変数が代入されるまで ロングハンドプロパティの値が決定できないことを示します。
この値は通常通りカスケードされ、 算出値時に(var()関数が最終的に代入された後)、 ショートハンドがパースされ、ロングハンドに適切な値が与えられます。
テスト
注: ショートハンドが var() なしで書かれている場合、 パース時に分解されて構成する ロングハンドプロパティ に分けられます。 その後ロングハンドが カスケードに参加し、 ショートハンドプロパティはほぼ破棄されます。 一方、ショートハンドに var() が含まれている場合は、 var() が何に代入されるかわからないため、 この処理はできません。
代入保留値は、APIで観測可能な場合は空文字列としてシリアライズされなければなりません。
テスト
ショートハンドプロパティは、 構成するロングハンドプロパティの値を集めて、 それらと同じ値集合にパースされる値を合成することでシリアライズされます。
あるショートハンドに属するすべてのロングハンドプロパティが、 同じ元のショートハンド値由来の代入保留値の場合、 ショートハンドプロパティはその元の(var()を含む)値としてシリアライズされなければなりません。
それ以外の場合、 あるショートハンドに属するロングハンドプロパティのいずれかが代入保留値である場合、 またはまだ代入されていないvar()関数を含む場合、 ショートハンドプロパティは空文字列としてシリアライズされなければなりません。
3.3. 長すぎる変数の安全な扱い
単純な実装では、var()関数が「ビリオンラフ攻撃」のバリエーションに使われる可能性があります:
.foo {
--prop1 : lol;
--prop2 : var ( --prop1) var ( --prop1);
--prop3 : var ( --prop2) var ( --prop2);
--prop4 : var ( --prop3) var ( --prop3);
/* etc */
}
この短い例では、--prop4の算出値はlol lol lol lol lol lol lol lolとなり、 元のlolが8回繰り返されます。 レベルを1つ増やすごとに識別子の数は倍増します。 これを30レベルまで拡張すると(手作業でも数分で可能)、 --prop30にはほぼ10億回識別子が現れることになります。
この種の攻撃を防ぐため、 UAはvar()関数が展開するトークン列の長さに UA定義の制限を課さなければなりません。 var()がこの制限より長いトークン列に展開される場合、 そのプロパティは算出値時に無効となります。
この仕様ではどの程度の制限を課すべきかは定義されていません。 ただし、1KB以上のテキストを含むカスタムプロパティにも妥当なユースケースがあるため、 制限値は比較的高く設定することが推奨されます。
注: UAがリソース制約のために標準を逸脱することが許されるという一般原則は、 ここでも基本的に当てはまります。 UAは別途、サポート可能なカスタムプロパティの長さや識別子のサイズに上限を設けている場合もあります。 この節で特にこの攻撃を強調する理由は、 長い歴史があり、 一見してどの部分も大きすぎるようには見えなくても実行できてしまうためです。
4. API
すべてのカスタムプロパティの宣言は、大文字小文字区別フラグがセットされています。
注: カスタムプロパティはキャメルケース形式ではCSSStyleDeclarationオブジェクトに現れません。 なぜなら、その名前は大文字・小文字の両方を含み、 それぞれが異なるカスタムプロパティを示す場合があるからです。 自動キャメルケース化によるテキスト変換はこの仕様と両立できません。 それでも、getPropertyValue()などで正しい名前でアクセス可能です。
4.1. カスタムプロパティのシリアライズ
カスタムプロパティ名は、著者が指定したコードポイント列をそのまま(大文字小文字も変更せず)シリアライズしなければなりません。
注: 非カスタムプロパティの場合、 プロパティ名はASCII範囲に制限され、ASCII大文字小文字区別なしです。 そのため、実装では通常、名前を小文字化してシリアライズします。
カスタムプロパティの指定値は著者が指定した通り厳密にシリアライズしなければなりません。 他のプロパティで発生しうる単純化(コメント除去、空白正規化、数値トークンの再シリアライズなど)は行ってはなりません。
カスタムプロパティの算出値もまた著者が指定した通り厳密にシリアライズしなければなりません。 ただし、var()関数の置換を除きます。
テスト
--y : /* baz */ ; --x : /* foo */ var ( --y) /* bar */ ;
--xの指定値のシリアライズは
としなければならず、
算出値のシリアライズは
としなければなりません。
(値の先頭の空白はCSSパーサーによって自動でトリムされるため、ここには残りません。)
例えばUUIDをカスタムプロパティに格納する場合、 --uuid: 12345678-12e3-8d9b-a456-426614174000 のように、スクリプトでアクセスした際に指定どおりそのまま返される必要があります。
この値はCSSで隣接する数値や次元としてパースされます。 特に「-12e3」は数値としてパースされ、-12000となります。 他の文脈でCSSOMが要求するような形で再シリアライズすると、 著者の用途が致命的に壊れてしまいます。
5. 変更点
5.1. 2021年11月11日 CRドラフト以降の変更点
-
カスタムプロパティが全ての疑似要素(制限付きプロパティリストを持つものも含む)に適用されることを明確化
-
結合文字や合字などの問題を説明する例を追加
-
異なるコードポイント列を使って似た名前の変数が現れる場合の文言を強化
-
より視覚的に区別しやすい言語(英語とギリシャ語)を例に使って例を明確化
-
セキュリティとプライバシーの章を分離
5.2. 2015年12月3日 CR以降の変更点
-
[css-syntax-3] が宣言値の先頭・末尾の空白を自動でトリムするようになったため、 カスタムプロパティの文法で<declaration-value>を省略可能にし、 空の変数も許容されるようにした。 (Issue 774)
-
同様に、var()の空のフォールバックも有効化
-
'-'プロパティはCSSによる将来の利用のため予約された
-
「animation-tainted」の概念を追加し、 非アニメーション可能プロパティが変数を使ってアニメーション可能性を持ち込むことを防止
-
カスタムプロパティの初期値や循環・代入失敗時の結果をより明確にするため、 保証された無効値の概念を導入し、 フォールバックで最終的に捕捉されるまで失敗を代入に伝播できるようにした。
-
循環が発生した場合、算出値時に無効の挙動を引き起こすことを定義
-
変数がCSS全体キーワードに解決可能になった(フォールバック指定時のみ)
-
登録済みカスタムプロパティが 算出値時に無効の場合に非カスタムプロパティと同様に動作することを明確化
-
var()を含むロングハンドでも、 保留値を持つロングハンド同様にショートハンドのシリアライズ不可を引き起こすようにした
-
指数的代入攻撃への防御をUAに求めるようにした
-
カスタムプロパティの値のシリアライズ方法を定義(以前はプロパティ名のみだった)
5.3. 2014年5月6日 Last Call Working Draft以降の変更点
-
ショートハンドが変数を使う場合のロングハンドのシリアライズ方法を定義
-
DOMの「大文字小文字区別」定義へのリンクを追加
-
:lang()と変数を使った簡易i18nの例を追加
-
カスタムプロパティ内でvar()を使う場合は、var()の文法に沿う必要があることを明確化
6. 謝辞
CSSワーキンググループの多くの方々に、長年にわたり変数の夢を守り続けてくださったことに深く感謝します。 特に Daniel Glazman 氏と David Hyatt 氏に感謝します。 メーリングリストでも多くの方々がこの変数仕様の発展に寄与してくださいました。 特に Brian Kardell 氏、 David Baron 氏、 François Remy 氏、 Roland Steiner 氏、 そして Shane Stephens 氏に感謝します。
7. プライバシーに関する考慮事項
この仕様は、著者が自身で管理するページ内でスタイリング情報を渡すための純粋に著者レベルの仕組みを定義します。 そのため、新しいプライバシーに関する懸念事項はありません。
8. セキュリティに関する考慮事項
§ 3.3 長すぎる変数の安全な扱いは、 var()関数など 「マクロ展開」的な仕組みに対して成立しうる、長年知られるDoS攻撃について指摘し、 それへの防御策を義務付けています。