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 只需要少量、已知的回溯能力, 这使得更高效的解析算法成为可能, 因此无限回溯在浏览器实现中通常被认为是不可接受的。
2.1. 语法
样式规则的内容现在接受嵌套样式规则和at 规则, 以及现有的声明。
嵌套样式规则 与非嵌套规则的不同之处在于:
嵌套样式规则的解析的具体细节定义在[CSS-SYNTAX-3]中。
CSS 工作组目前正在探索解析前瞻的后果, 并可能因此调整允许的语法。[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; } */ /* & 可以在单个选择器中使用多次 */ .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; } */ /* 层叠层的示例用法 */ @layer base{ html{ block-size : 100 % ; & body{ min-block-size : 100 % ; } } } /* 等同于 @layer base { html { block-size: 100%; } html body { min-block-size: 100%; } } */ /* 层叠层的嵌套示例 */ @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. 嵌套其他 At 规则
除了嵌套样式规则, 本规范还允许在样式规则中嵌套嵌套组规则: 任何其主体包含样式规则的 at 规则都可以嵌套在 样式规则中。
以这种方式嵌套时, 嵌套组规则的内容会被解析为<样式块>而不是<样式表>:
这些嵌套组规则的含义和行为与非嵌套时相同, 除非另有规定。
/* 属性可以直接使用 */ .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; } } */ /* 层叠层嵌套示例 */ 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
对象
将在其
属性中包含一个对象,
其中包含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. 混合嵌套规则与声明
当样式规则同时包含声明 和嵌套样式规则或嵌套条件组规则时, 这三者可以任意混合。 然而,声明和其他规则的相对顺序不会以任何方式保留。
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 AMPERSAND)。
在嵌套样式规则的选择器中使用时, 嵌套选择器 表示父规则匹配的元素。 在其他任何上下文中使用时, 它表示与该上下文中的: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 > 蓝色文本</ 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{ ...}
在这里,三个级别的嵌套, 每个列表中有三个选择器, 产生了27个简化后的选择器。 向列表中添加更多选择器, 增加更多的嵌套层次, 或使嵌套规则更复杂 可能会使相对较小的规则 扩展为多兆字节的选择器 (或更多!)。
一些 CSS 工具通过启发式丢弃某些变体 来避免这种问题, 这样它们就不必输出那么多, 但仍然可能是正确的, 但这不是用户代理可用的选项。
通过使用:is() 进行简化完全消除了这个问题, 代价是使特异性稍微有些没用, 这被认为是一个合理的权衡。
嵌套选择器 允许出现在复合选择器中的任何位置, 甚至可以在类型选择器之前, 这违反了复合选择器中的正常排序限制。
div
元素”。
它也可以写为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 规则中移除index位置的 CSS 规则,移除 CSS 规则。
注意: 带有嵌套规则的CSSStyleRule
的序列化
已由[CSSOM]很好地定义,
通过序列化 CSS 规则。
注意: 在嵌套样式规则选择器可以以什么开头的限制
在插入 CSS 规则步骤 5 中被视为“CSS 施加的约束”
(当调用插入 CSS 规则时适用,不仅仅是CSSStyleRule
本身)。
当设置selectorText
时,
如果CSSStyleRule
是嵌套样式规则,
且返回的选择器组
以选择器开头,该选择器以标识符或函数 token 开头,
则不执行任何操作并返回。