CSS の `@property` ルールでカスタムプロパティを定義する
CSS の @property ルールを使うことで、CSS のカスタムプロパティを定義できます。カスタムプロパティを定義することでプロパティの構文チェック、デフォルト値の設定、プロパティが値を継承するかどうかの設定などが可能になります。カスタムプロパティに誤った値が代入されることを防いだり、グラデーションのアニメーションなど、従来は実装が難しかった機能をサポートすることができます。
CSS の @property
ルールを使うことで、CSS のカスタムプロパティ を定義できます。カスタムプロパティを定義することでプロパティの構文チェック、初期値の設定、プロパティが値を継承するかどうかの設定が可能になります。
@property --my-color {
/** red や #fff のように色のキーワードまたはカラーコードを代入可能 */
syntax: "<color>";
inherits: false;
initial-value: red;
}
/* カスタムプロパティを使用 */
.my-element {
--my-color: blue;
color: var(--my-color);
}
@property
ルールのメリット
@property
ルールを使うことで、以下のようなメリットがあります。
- カスタムプロパティによりコードをドキュメント化する
- 誤った値の代入を防ぎ、適切なエラーメッセージを表示する
- グラデーションのアニメーションなど、従来は実装が難しかった機能をサポートする
コードのドキュメント化・誤った値の代入を防ぐ
通常のカスタムプロパティの定義は以下のような記法で定義されます。
:root {
--primary: #7bd389;
--secondary: #607466;
}
:root
にカスタムプロパティを定義することで、コードベース全体で使用できるカスタムプロパティを定義できます。このような方法はプロダクトで統一されたテーマを適用したい場合によく使われます。ボタンの background-color
に #7bd389
と記述されているよりも、カスタムプロパティを使用して var(--primary)
とあるほうが意味がより伝わりやすいというメリットがあります。また、テーマを変更したくなった場合に、--primary
の値を変更するだけで全体のテーマを変更できるため、保守性が高くなります。
一方でカスタムプロパティは意図しない値が代入される危険があります。ここでは --primary
という変数には色の値が入るべきであるという前提がありますが、誤って --primary
に文字列や数値が代入されてしまうと、意図しない挙動を引き起こす可能性があります。
例えば --primary
というカスタムプロパティがすでに存在することを知らずに、同じ名前のカスタムプロパティでうっかり上書きしてしまうかもしれません。
.card {
--primary: 100px;
width: var(--primary);
}
カスタムプロパティが複数の場所で定義されていた場合には、最も近いスコープのカスタムプロパティが優先されます。もし .ca rd
クラスの配下で --primary
に色の値が代入されていることを前提としていたコードが記述されていた場合、思わぬデザイン崩れが発生してしまうかもしれません。
:root {
--primary: #7bd389;
--secondary: #607466;
}
.card {
--primary: 100px;
width: var(--primary);
}
/** button.css */
.button {
/* 意図せず無効な値が代入されてしまっている */
background-color: var(--primary);
}
@property
ルールでは syntax
フィールドにデータ型を指定することで、カスタムプロパティに代入される値の型を制限できます。:root
で行っていたカスタムプロパティの定義は以下のように記述できます。
@property --primary {
syntax: "<color>";
inherits: true;
initial-value: #7bd389;
}
@property --secondary {
syntax: "<color>";
inherits: true;
initial-value: #607466;
}
第一に syntax
フィールドに <color>
と指定されているおかげで、開発者は --primary
に代入される値がカラーコードであることをコードから読み取ることができます。静的型付け言語のようにコードをドキュメント化できるという点がメリットと言えるでしょう。
もし --primary
に無効な値、つまりカラーコード以外の値が代入された場合には、プロパティの値は初期値(initial-value
で設定した値)にフォールバックします。下記のコードでは .card
クラスの --primary
に 100px
が代入されていますが、これは無効な値であるため、--primary
の値は初期値である #7bd389
にフォールバックします。
@property --primary {
syntax: "<color>";
inherits: true;
initial-value: #7bd389;
}
@property --secondary {
syntax: "<color>";
inherits: true;
initial-value: #607466;
}
.card {
/** この値は `<color>` というデータ型に一致しないため */
--primary: 100px;
/** 初期値にフォールバックされるため、`var(--primary)` は `#7bd389` になる */
width: var(--primary);
}
このように開発者はすぐにコードの誤りに気づくことができ、意図せぬデザイン崩れを未然に防ぐことができます。Developer Tools の Styles タブを見ると、誤った値が代入された場合には invalid
と表示されるていることがわかります。適切なエラーメッセージを表示することで、開発者が問題を解決する手助けとなります。
グラデーションのアニメーションなど、従来は実装が難しかった機能をサポートする
CSS でグラデーションのアニメーションを実装する例を考えてみましょう。通常のカスタムプロパティを使って実装では以下のようなコードになるでしょう。
:root {
--color1: #7bd389;
--color2: #607466;
--angle: 0deg;
}
@keyframes gradient {
0% {
--angle: 0deg;
}
100% {
--angle: 360deg;
}
}
.gradient {
width: 100px;
height: 100px;
background: linear-gradient(var(--angle), var(--color1), var(--color2));
animation: gradient 3s linear infinite;
}
@keyframes
ルールでグラデーションのアニメーションを定義し、--angle
カスタムプロパティがアニメーションの進行度に応じて増加するようにしています。.gradient
クラスでは animation
プロパティで gradient
アニメーションを適用し、3 秒かけて 360 度回転するグラデーションを実装しています。
しかし、このコードは期待と反して正しく動作しません。
これは CSS のアニメーションがすべてのカスタムプロパティを文字列として解析しているためです。0deg
→ 360deg
を数値の増加として解釈できないため、アニメーションが正しく動作しないのです。
@property
ルールを使い、<angle>
というデータ型を指定することで、この問題を解決できます。
@property --angle {
syntax: "<angle>";
inherits: true;
initial-value: 0deg;
}
このように記述することで、--angle
に代入される値が角度であることを明示できるため、正しくブラウザによって解釈されるようになります。下記の例でグラデーションのアニメーションが行われていることが確認できます。
カスタムプロパティによる構文チェック
@property
ルールの syntax
フィールドには許容される構文を指定します。記述できる構文は、あらかじめ定義されたデータ型、もしくは記号を使ってデータ型拡張して指定できます。
あらかじめ定義されたデータ型は以下のようなものがあります。
<length>
<number>
<percentage>
<length-percentage>
<color>
<image>
<url>
<integer>
<angle>
<time>
<resolution>
<transform-function>
<custom-ident>
例えば <length>
は長さの値を表すデータ型で、width
や height
、margin
などのプロパティに使用されます。<length>
は 42px
、1.5rem
、100%
などのように <number>
型と 1 つ以上の単位を組み合わせたものです。
記号を使ったデータ型の拡張
あらかじめ定義されたデータ型に加えて、以下の記号を使用してデータ型を指定できます。
+
:スペースで区切られたリスト#
:カンマで区切られたリスト|
:or で複数のデータ型を指定
例として <length>+
と定義されたデータ型は 1rem 2rem 3rem
のように padding
や margin
などのプロパティに使用される複数の長さの値を表します。
@property --my-padding {
syntax: "<length>+";
inherits: true;
initial-value: 0;
}
.my-element {
--my-padding: 1rem 2rem 3rem;
padding: var(--my-padding);
}
<color>#
と定義されたデータ型は #7bd389, #607466
のようにカンマで区切られた複数の色の値を表します。これは rgp()
や hsl()
などの色の値を複数指定する際に使用されます。
@property --my-color {
syntax: "<color>#";
inherits: true;
initial-value: #7bd389;
}
.my-element {
--my-color: #7bd389, #607466;
background: linear-gradient(to right, var(--my-color));
}
<length>|<percentage>
と定義されたデータ型は 1rem | 100%
のように長さとパーセンテージのどちらかの値を表します。これは width
や height
などのプロパティに使用されます。
@property --my-size {
syntax: "<length>|<percentage>";
inherits: true;
initial-value: 100%;
}
.my-element {
--my-size: 1rem | 100%;
width: var(--my-size);
}
red | blue | green
のように具体的な値を指定することもできます。
@property --my-color {
syntax: "red | blue | green";
inherits: true;
initial-value: red;
}
.my-element {
--my-color: blue;
color: var(--my-color);
}
*
あらゆるデータ型を指定する syntax
に *
を指定することで、あらゆるデータ型を指定できます。この場合、カスタムプロパティにはどのような値が代入されてもエラーが発生しません。
@property --my-value {
syntax: "*";
inherits: true;
initial-value: 0;
}
.my-element1 {
--my-value: 24px;
font-size: var(--my-value);
}
.my-element2 {
--my-value: blue;
background-color: var(--my-value);
}
カスタムプロパティの値の継承を防ぐ
@property
ルールの inherits
フィールドに false
を指定することでカスタムプロパティに代入した値の継承を防ぐことができます。inherits
フィールドに false
を指定した場合、カスタムプロパティに代入された値はその要素のみに適用され、その要素の子孫要素には適用されません。
例を見てみましょう。inherits
フィールドに true
を指定した場合、.parent
クラスに --my-color
に blue
が代入されているため、.child
クラスにも blue
が適用されます。これは通常の方法でカスタムプロパティを定義した場合と同じ挙動です。
inherits
フィールドに false
を指定した場合、子要素の .child
クラスには --my-color
の初期値である red
が適用されます。
inherits: false
を指定することで意図せぬ子要素に影響を与えることを防ぐことができます。
まとめ
@property
ルールを使うことで、カスタムプロパティに対してデータ型の指定、初期値の設定、値の継承の有無を設定できる- カスタムプロパティに対してデータ型を指定することで、誤った値の代入を防ぎ、適切なエラーメッセージを表示することができる
@property
ルールでデータ型を指定することでグラデーションのアニメーションなど従来は実装が難しかった機能をサポートすることができる