1. はじめに
このセクションは規範的ではありません。
このモジュールは、スタイル規則を他のスタイル規則の中に入れ子にすることをサポートし、 内側の規則のセレクターが外側の規則で一致した要素を参照できるようにします。 この機能により、関連するスタイルをCSS文書内の単一の構造にまとめることができ、 可読性と保守性が向上します。
1.1. モジュールの相互作用
このモジュールは、[CSS21] パーサーモデルを拡張する新しいパーサー規則を導入します。 また、[SELECTORS4] モジュールを拡張するセレクターも導入します。
1.2. 値
この仕様では新しいプロパティや値は定義されていません。
1.3. 動機
CSSは、多少複雑なウェブページの場合でも、 関連するコンテンツをスタイリングするために多くの重複を含むことがよくあります。 例えば、これは [CSS-COLOR-3] モジュールのあるバージョンのCSSマークアップの一部です:
table.colortable td{ text-align : center; } table.colortable td.c{ text-transform : uppercase; } table.colortable td:first-child, table.colortable td:first-child+td{ border : 1 px solid black; } table.colortable th{ text-align : center; background : black; color : white; }
入れ子を使うと、関連するスタイル規則を次のようにまとめることができます:
table.colortable{ & td{ text-align : center; &.c{ text-transform : uppercase} &:first-child, &:first-child + td{ border : 1 px solid black} } & th{ text-align : center; background : black; color : white; } }
重複を減らせるだけでなく、 関連する規則のグループ化によって、CSSの可読性と保守性が向上します。
2. スタイル規則の入れ子
スタイル規則は他のスタイル規則の中に入れ子にできます。 これらの 入れ子スタイル規則 は通常のスタイル規則と同様に、 セレクターを使って要素にプロパティを関連付けますが、親規則のセレクターコンテキストを「継承」し、 親のセレクターを繰り返すことなく、さらにその上に構築することができます。
入れ子スタイル規則は、通常のスタイル規則とほぼ同じですが、 その セレクターは 識別子や 関数表記で始めることはできません。 さらに、入れ子スタイル規則は 相対セレクターを使用することができます。
.foo{ color : red; .bar{ color : blue; } }
有効であり、次と同等になります:
.foo{ color : red; } .foo .bar{ color : blue; }
入れ子規則では 入れ子セレクター を使って親規則で一致した要素を直接参照したり、 相対セレクター構文を使って「子孫」以外の関係も指定できます。
.foo{ color : red; &:hover{ color : blue; } } /* 次と同等: */ .foo{ color : red; } .foo:hover{ color : blue; }
.foo{ color : red; + .bar{ color : blue; } } /* 次と同等: */ .foo{ color : red; } .foo + .bar{ color : blue; }
div{ color : red; input{ margin : 1 em ; } } /* 「input」は識別子なので無効 */
このようなセレクターも書くことはできますが、少し書き換える必要があります:
div{ color : red; & input{ margin : 1 em ; } /* 有効、識別子で始まらない */ :is ( input) { margin : 1 em ; } /* 有効、コロンで始まり、 前の規則と同等 */ }
なぜ入れ子規則のセレクターに制約があるのですか?
スタイル規則を他のスタイル規則の中に素直に入れ子にすると、残念ながら曖昧になります。セレクターの構文が宣言の構文と重複するため、 パーサーは宣言なのかスタイル規則の始まりなのかを判断するために無限の先読みが必要になります。
例えば、パーサーが color:hover ... を見たとき、
それが color プロパティ
(無効な値が設定されている...)
なのか、<color>
要素のセレクターなのか、区別できません。
有効なプロパティを探すことで判別することもできません。
実装がサポートするプロパティによってパースが変わってしまい、時期によっても変化してしまうためです。
入れ子スタイル規則が 識別子で始まることを禁止することでこの問題を回避できます。 宣言はすべて識別子で始まり、 その識別子がプロパティ名となるため、 パーサーはすぐに 宣言か スタイル規則か判断できます。
一部の非ブラウザー実装ではこの制約を課していません。 多くの場合、最終的にはプロパティとセレクターを区別できますが、 それにはパーサーが無限の先読みを必要とします。 つまり、どちらとして解釈するべきか判断できるまで不明な量の内容を保持しなければなりません。 CSSはこれまで、パース時の先読み量が少なく、既知であることを要求してきました。 これにより効率的なパースアルゴリズムが可能となり、 無限先読みはCSSのブラウザー実装では一般に容認されていません。
2.1. 構文
スタイル規則の内容には、従来の宣言に加えて、新たに入れ子スタイル規則やアットルールが許可されます。
入れ子スタイル規則は、非入れ子規則と次の点で異なります:
-
入れ子スタイル規則は、前置部として<relative-selector-list>を受け入れます (従来の<selector-list>だけでなく)。 相対セレクターはすべて、入れ子セレクターで表される要素を基準に解釈されます。
-
<relative-selector-list>内のセレクターが結合子で始まらず、かつ入れ子セレクターを含む場合、 それは非相対セレクターとして解釈されます。
入れ子スタイル規則のパース方法の詳細は[CSS-SYNTAX-3]で定義されています。
CSSWGは現在、パース時の先読みの影響を検討しており、 その結果として許可される構文を調整する可能性があります。[Issue #7961]
無効な入れ子スタイル規則は、その内容ごと無視されますが、親規則自体は無効になりません。
/* & は単独でも利用可能 */ .foo{ color : blue; & > .bar{ color : red; } > .baz{ color : green; } } /* 同等: .foo { color: blue; } .foo > .bar { color: red; } .foo > .baz { color: green; } */ /* 複合セレクターで、親のセレクターを絞り込む */ .foo{ color : blue; &.bar{ color : red; } } /* 同等: .foo { color: blue; } .foo.bar { color: red; } */ /* セレクターリストの複数のセレクターはすべて親に相対 */ .foo, .bar{ color : blue; + .baz, &.qux{ color : red; } } /* 同等: .foo, .bar { color: blue; } :is(.foo, .bar) + .baz, :is(.foo, .bar).qux { color: red; } */ /* & は1つのセレクター内で複数回使用可能 */ .foo{ color : blue; & .bar & .baz & .qux{ color : red; } } /* 同等: .foo { color: blue; } .foo .bar .foo .baz .foo .qux { color: red; } */ /* & はセレクターの先頭でなくてもよい */ .foo{ color : red; .parent &{ color : blue; } } /* 同等: .foo { color: red; } .parent .foo { color: blue; } */ .foo{ color : red; :not ( &) { color : blue; } } /* 同等: .foo { color: red; } :not(.foo) { color: blue; } */ /* 相対セレクター を使う場合、 先頭の & は自動的に暗黙指定される */ .foo{ color : red; + .bar + &{ color : blue; } } /* 同等: .foo { color: red; } .foo + .bar + .foo { color: blue; } */ /* ちょっと変ですが、& 単独でも利用可能 */ .foo{ color : blue; &{ padding : 2 ch ; } } /* 同等: .foo { color: blue; } .foo { padding: 2ch; } // または .foo { color: blue; padding: 2ch; } */ /* さらに、& の重複も可能 */ .foo{ color : blue; &&{ padding : 2 ch ; } } /* 同等: .foo { color: blue; } .foo.foo { padding: 2ch; } */ /* 親セレクターは任意に複雑にできる */ .error, #404{ &:hover > .baz{ color : red; } } /* 同等: :is(.error, #404):hover > .baz { color: red; } */ .ancestor .el{ .other-ancestor &{ color : red; } } /* 同等: .other-ancestor :is(.ancestor .el) { color: red; } /* 入れ子セレクターも複雑にできる */ .foo{ &:is ( .bar, &.baz) { color : red; } } /* 同等: .foo :is(.bar, .foo.baz) { color: red; } */ /* 入れ子の多段構造ではセレクターが積み重なる */ figure{ margin : 0 ; > figcaption{ background : hsl ( 0 0 % 0 % /50 % ); > p{ font-size : .9 rem ; } } } /* 同等: figure { margin: 0; } figure > figcaption { background: hsl(0 0% 0% / 50%); } figure > figcaption > p { font-size: .9rem; } */ /* Cascade Layersの利用例 */ @layer base{ html{ block-size : 100 % ; & body{ min-block-size : 100 % ; } } } /* 同等: @layer base { html { block-size: 100%; } html body { min-block-size: 100%; } } */ /* Cascade Layersの入れ子利用例 */ @layer base{ html{ block-size : 100 % ; @layer support{ & body{ min-block-size : 100 % ; } } } } /* 同等: @layer base { html { block-size: 100%; } } @layer base.support { html body { min-block-size: 100%; } } */ /* Scopingの利用例 */ @scope ( .card) to( > header) { :scope{ inline-size : 40 ch ; aspect-ratio : 3 /4 ; > header{ border-block-end : 1 px solid white; } } } /* 同等: @scope (.card) to (> header) { :scope { inline-size: 40ch; aspect-ratio: 3/4; } :scope > header { border-block-end: 1px solid white; } } */ /* Scopingの入れ子利用例 */ .card{ inline-size : 40 ch ; aspect-ratio : 3 /4 ; @scope ( &) to( > header > *) { :scope > header{ border-block-end : 1 px solid white; } } } /* 同等: .card { inline-size: 40ch; aspect-ratio: 3/4; } @scope (.card) to (> header > *) { :scope > header { border-block-end: 1px solid white; } } */
ただし、次のような場合は無効です:
/* セレクターが識別子で始まる */ .foo{ color : blue; div{ color : red; } }
例えば、あるコンポーネントが.fooクラスを使用し、 入れ子のコンポーネントが.fooBarを使用する場合、 Sassでは次のように書けます:
.foo{ color : blue; &Bar{ color : red; } } /* Sassでは同等: .foo { color: blue; } .fooBar { color: red; } */
しかし、この文字列ベースの解釈は、 著者が入れ子規則で型セレクターを追加しようとした場合に曖昧になります。例えばBarは HTMLのカスタム要素名として有効です。
CSSはこうした連結を行わず、 入れ子セレクターの各部を個別に解釈します:
.foo{ color : blue; &Bar{ color : red; } } /* CSSでは同等: .foo { color: blue; } Bar.foo { color: red; } */
2.2. 他のアットルールの入れ子
入れ子スタイル規則に加えて、 この仕様では入れ子グループ規則を スタイル規則の中に入れ子にすることを許可します。 本体にスタイル規則を含むアットルールは、スタイル規則内に入れ子にすることができます。
このように入れ子にされた場合、 入れ子グループ規則の内容は <style-block>としてパースされ、 <stylesheet>とは異なります:
-
プロパティは直接利用でき、
& {...}
ブロックに入れ子にされているかのように動作します。
これら入れ子グループ規則の意味や挙動は、特に指定がない限り変更されません。
/* プロパティを直接利用可能 */ .foo{ display : grid; @media ( orientation: landscape) { grid-auto-flow : column; } } /* 同等: .foo { display: grid; @media (orientation: landscape) { & { grid-auto-flow: column; } } } */ /* 最終的な同等: .foo { display: grid; } @media (orientation: landscape) { .foo { grid-auto-flow: column; } } */ /* 条件付き規則はさらに入れ子にできる */ .foo{ display : grid; @media ( orientation: landscape) { grid-auto-flow : column; @media ( min-width >1024 px ) { max-inline-size : 1024 px ; } } } /* 同等: .foo { display: grid; } @media (orientation: landscape) { .foo { grid-auto-flow: column; } } @media (orientation: landscape) and (min-width > 1024px) { .foo { max-inline-size: 1024px; } } */ /* Cascade Layersの入れ子例 */ html{ @layer base{ block-size : 100 % ; @layer support{ & body{ min-block-size : 100 % ; } } } } /* 同等: @layer base { html { block-size: 100%; } } @layer base.support { html body { min-block-size: 100%; } } */ /* Scopingの入れ子例 */ .card{ inline-size : 40 ch ; aspect-ratio : 3 /4 ; @scope ( &) { :scope{ border : 1 px solid white; } } } /* 同等: .card { inline-size: 40ch; aspect-ratio: 3/4; } @scope (.card) { :scope { border-block-end: 1px solid white; } } */
直接入れ子にされたプロパティはすべて、
まとめて順に集められ、
入れ子スタイル規則
(セレクター&)として入れ子規則の先頭に配置されます。
OMでも同様です。
(つまり、childRules
属性は
まずこの入れ子スタイル規則から始まり、
直接入れ子にされたすべてのプロパティを含みます。)
例えば、先ほどの例:
.foo{ display : grid; @media ( orientation: landscape) { grid-auto-flow : column; } } /* 同等: .foo { display: grid; @media (orientation: landscape) { & { grid-auto-flow: column; } } } */
は、まさに同等であり、CSSOM構造も全く同じになります。
CSSMediaRule
オブジェクトには
CSSStyleRule
オブジェクトが1つあり、
その
属性内に
grid-auto-flowプロパティが含まれます。
注: このため、このような規則のシリアライズは 元の記述方法と異なり、 シリアライズには直接入れ子のプロパティが一切含まれません。
2.2.1. 入れ子の @scope 規則
@scope規則が入れ子グループ規則の場合、 &は <scope-start>セレクター内で、 最も近い祖先のスタイル規則で一致した要素を参照します。
本体のスタイル規則や自身の<scope-end> セレクターにおいては、 @scope規則は祖先スタイル規則として扱われ、 <scope-start>で一致した要素をマッチします。
.parent{ color : blue; @scope ( & > .scope) to( & .limit) { & .content{ color : red; } } }
次のように同等です:
.parent{ color : blue; } @scope ( .parent > .scope) to( .parent > .scope .limit) { .parent > .scope .content{ color : red; } }
2.3. 入れ子規則と宣言の混合
スタイル規則内に宣言と 入れ子スタイル規則や 入れ子条件付きグループ規則が混在している場合、 3つは自由に混ぜることができます。 ただし、宣言と他の規則との相対的な順序は保持されません。
article{ color : green; &{ color : blue; } color: red; } /* 同等 */ article{ color : green; color : red; &{ color : blue; } }
出現順序を決定する際、 入れ子スタイル規則や 入れ子条件付きグループ規則は親規則の後に来るものとみなされます。
article{ color : blue; &{ color : red; } }
両方の宣言は同じ詳細度 (0,0,1) ですが、 入れ子規則は親規則の後とみなされるため、 color: red宣言がカスケードで勝ちます。
一方、次の例では:
article{ color : blue; :where ( &) { color : red; } }
:where()疑似クラスは入れ子セレクターの詳細度を0にするため、 color: red宣言の詳細度は(0,0,0)となり、 color: blue宣言が勝ちます。 この場合は「出現順序」が考慮される前に決着します。
注: 宣言と入れ子規則を自由に混ぜることはできますが、 読みにくく混乱しやすいので、 全てのプロパティはスタイル規則の先頭にまとめ、 入れ子規則は後に書くことが推奨されます。 (古いユーザーエージェントでもこのほうが挙動が良いです。 パースとエラー回復の仕様により、入れ子規則の後に書かれたプロパティは無視されることがあります。)
注: 他の規則と同様、 入れ子がある場合のスタイル規則のシリアライズは元の記述と異なります。 特に、直接入れ子のプロパティは すべて入れ子規則より前にシリアライズされるため、 先にプロパティを書くべきもう一つの理由となります。
3. 入れ子セレクター:& セレクター
入れ子スタイル規則を使用する際、 親規則で一致した要素を参照できる必要があります。 これはつまり、入れ子の主目的です。 これを実現するために、 本仕様は新しいセレクター、 入れ子セレクター を定義します。記述は & (U+0026 アンパサンド) です。
入れ子スタイル規則のセレクター内で使用した場合、 入れ子セレクターは親規則で一致した要素を表します。 他の文脈で使用した場合は、同じ文脈での:scopeと同じ要素を表します (特に定義がなければ)。
a, b{ & c{ color : blue; } }
は次と同等です:
:is ( a, b) c{ color : blue; }
入れ子セレクターは 擬似要素を表すことはできません (:is()疑似クラスと同様の挙動)。
.foo, .foo::before, .foo::after{ color : red; &:hover{ color : blue; } }
&は .fooで一致した要素のみを表し、 次のように同等です:
.foo, .foo::before, .foo::after{ color : red; } .foo:hover{ color : blue; }
この制約を緩和したいと考えていますが、 :is()と &の両方を同時に変更する必要があります。 どちらも同じ基盤の仕組みを意図して利用しているためです。 (Issue 7433)
詳細度は 入れ子セレクターの場合、 親スタイル規則のセレクターリストの中で最大の詳細度に等しくなります (:is()と同様の挙動)。
#a, b{ & c{ color : blue; } } .foo c{ color : red; }
次のようなDOM構造の場合:
< b class = foo > < c > Blue text</ c > </ b >
このテキストは青色になります。 &の詳細度は #a([1,0,0])と b([0,0,1])のうち大きい方で[1,0,0]、 & cセレクター全体では[1,0,1]となり、 .foo c([0,1,1])より大きくなります。
これは、入れ子を手動展開した場合の結果とは異なります。 展開した場合はcolor: blueは b c([0,0,2])で一致し、 #a c([1,0,1])ではありません。
なぜ入れ子規則の詳細度は非入れ子規則と違うのですか?
入れ子セレクターは意図的に :is()疑似クラスと同じ詳細度ルールを使用します。 これは、実際に一致したセレクターではなく、引数の中で最大の詳細度を使うというものです。
これはパフォーマンス上の理由です。 複数の詳細度を持つセレクターが一致方法によって変化する場合、 セレクターの一致処理が非常に複雑かつ遅くなります。
では、なぜ&を:is()ベースで定義するのでしょうか? 一部の非ブラウザー実装では:is()に展開せず、 直接展開しています(多くは:is()導入前から存在)。 ただし、この方法には深刻な問題があり、 一部のよくあるケースで膨大なセレクターが生成されてしまいます。
.a1, .a2, .a3{ .b1, .b3, .b3{ .c1, .c2, .c3{ ...; } } } /* 素直に展開すると */ .a1 .b1 .c1, .a1 .b1 .c2, .a1 .b1 .c3, .a1 .b2 .c1, .a1 .b2 .c2, .a1 .b2 .c3, .a1 .b3 .c1, .a1 .b3 .c2, .a1 .b3 .c3, .a2 .b1 .c1, .a2 .b1 .c2, .a2 .b1 .c3, .a2 .b2 .c1, .a2 .b2 .c2, .a2 .b2 .c3, .a2 .b3 .c1, .a2 .b3 .c2, .a2 .b3 .c3, .a3 .b1 .c1, .a3 .b1 .c2, .a3 .b1 .c3, .a3 .b2 .c1, .a3 .b2 .c2, .a3 .b2 .c3, .a3 .b3 .c1, .a3 .b3 .c2, .a3 .b3 .c3{ ...}
このように、3段階の入れ子で各リストに3つずつセレクターがあると、27個の展開セレクターが生成されます。 リストや入れ子レベルを増やしたり、規則を複雑にすると、 小さな規則がメガバイト級のセレクター(あるいはもっと膨大な量)に膨れ上がることがあります。
一部CSSツールは最悪のケースを避けるため、いくつかの組み合わせをヒューリスティックに省くことがありますが、 これはUA(ブラウザー)では選択できません。
:is()で展開することで、この問題を完全に回避できます。 詳細度の実用性が多少下がるものの、合理的なトレードオフと判断されました。
入れ子セレクターは 複合セレクター内のどこでも使えます。 型セレクターの前でも使えます。 複合セレクターの通常の並び順の制約を破ることができます。
同じ意味でdiv&とも書けますが、 これは入れ子スタイル規則の先頭としては無効ですが、セレクターの途中なら使用可能です。
4. CSSOM
4.1. CSSStyleRule
への修正
CSSスタイル規則は入れ子規則を持てるようになります:
partial interface CSSStyleRule { [SameObject ]readonly attribute CSSRuleList cssRules ;unsigned long insertRule (CSSOMString ,
rule optional unsigned long = 0);
index undefined deleteRule (unsigned long ); };
index
cssRules
属性は、
CSSRuleList
オブジェクトを子CSS規則として返さなければなりません。
insertRule(rule, index)
メソッドは、
CSS規則を挿入を呼び出してruleを
子CSS規則のindexに挿入した結果を返さなければなりません。
deleteRule(index)
メソッドは、
CSS規則の削除を
子CSS規則のindexで呼び出さなければなりません。
注: 入れ子規則を含むCSSStyleRule
の
シリアライズは
[CSSOM]で既に定義済みです。
CSS規則のシリアライズを参照してください。
注: 入れ子スタイル規則のセレクター先頭制約は
CSS規則を挿入の手順5「CSSによる制約」に該当します
(入れ子スタイル規則を受けるものに適用、CSSStyleRule
自体だけでなく)。
selectorText
を設定する際、
CSSStyleRule
が
入れ子スタイル規則で、
返されるセレクター群の先頭が識別子または関数トークンで始まる場合は何もせずリターンします。