【CSS in Depth 2 精译_056】8.4 CSS 的新特性——原生嵌套(Nesting)+ 8.5 本章小结
当前内容所在位置(可进入专栏查看其他译好的章节内容)
- 【第三部分 现代 CSS 代码组织】 ✔️
- 【第八章 层叠图层及其嵌套】 ✔️
- 8.1 用 layer 图层来操控层叠规则(上篇)
- 8.1.1 图层的定义(上篇)
- 8.1.2 图层的顺序与优先级(下篇)
- 8.1.3 revert-layer 关键字(下篇)
- 8.2 层叠图层的推荐组织方案
- 8.3 伪类 :is() 和 :where() 的用法
- 8.4 CSS 嵌套的使用 ✔️
- 8.4.1 嵌套选择器的使用 ✔️
- 8.4.2 深入理解嵌套选择器 ✔️
- 8.4.3 媒体查询及其他 @规则 的嵌套 ✔️
- 8.5 本章小结 ✔️
文章目录
- 8.4 嵌套 Nesting
- 8.4.1 嵌套选择器的使用 Using the nesting selector
- 8.4.2 深入理解嵌套选择器 Understanding the nuances of the nesting selector
- 8.4.2.1 嵌套对优先级的影响 Nesting affects specificity
- 8.4.2.2 嵌套对目标元素的影响 Nesting affects which elements are targeted
- 8.4.3 媒体查询及其他 @规则 的嵌套 Nesting media queries and other at-rules
- 8.4.3.1 始终先写非嵌套声明 Always place nonnested declarations before nested blocks
- 8.5 本章小结 Summary
《CSS in Depth》新版封面
译者按
因为临时有事耽搁,有段时间没更新专栏了,今天赶紧补上。原来书中的嵌套(nesting)并非是针对层叠图层(cascade layer)而言的,而是 CSS 新推出的一个语法糖(莫非是迫于 Sass 和 Less 的强大攻势?哈哈),还是等学完这一章再来统一更正前面的说法吧。对一个事物的认识就是这样螺旋上升的。一起学起来!
8.4 嵌套 Nesting
嵌套(Nesting) 是 CSS 的一项功能。它可以将一个样式规则放入另一个样式规则内,从而使子规则的选择器与父规则的选择器相对应,既减少了代码冗余,又可以将相关选择器组合到一起。如果之前用过 Sass
和 Less
等 CSS 预处理工具,就不会对这种写法感到陌生(不过也存在一些细微差别,稍后会详述)。
警告
嵌套是一项全新的功能。截至 2023 年底,嵌套已经在所有的主流浏览器中启用,不过部分用户可能需要过段时间才能安装升级。在决定是否在生产环境中使用嵌套特性前,请参考 https://caniuse.com/css-nesting 了解最新的使用情况统计信息。
下面举例说明嵌套功能的用法。我将为您展示一个同时启用嵌套功能和不启用的对比示例。如代码清单 8.13 所示,该样式代码为传统非嵌套的三个关联规则集。注意,这三个选择器都以相同的样式类 .card
开始。
代码清单 8.13 不带嵌套功能的 CSS 相关选择器
.card {padding: 1rem;border-radius: 0.5rem;background-color: #fff;
}.card > h3 {margin-block: 0;border-block-end: 1px solid #eee;
}.card .card-body {padding-block: 1em;
}
而用上嵌套后的代码将更简洁。代码清单 8.14 为上述代码的等效嵌套版。样式类选择器 .card
从第二、第三个选择器中移除,取而代之的,是将它们嵌套进第一个规则集内:
代码清单 8.14 嵌套 CSS 选择器
.card { /* 父级选择器 */padding: 1rem;border-radius: 0.5rem;background-color: #fff;> h3 { /* 选中 .card > h3 */margin-block: 0;border-block-end: 1px solid #eee;}.card-body { /* 选中 .card .card-body */padding-block: 1em;}
}
浏览器会将被嵌套的规则集选择器与外层选择器连接起来,因此其选中元素仍然与未使用嵌套的上一个版本相同。这样做有两个好处。首先,它消除了 .card
在所有三个选择器中的代码冗余,便于后续对该样式类进行修改、且更不容易出错;其次,嵌套的写法明确将相关样式归入同一组大括号内,表明它们都是协同工作的。其中的缩进是可选的,但一般都会保留,旨在强调代码结构。
而选择器的优先级则由最终组合的选择器共同决定,因此父选择器和子选择器的优先级是直接累加的。选择器 .card-body
的优先级加上其父选择器 .card
的优先级,即为 0, 2, 0
。换句话说,嵌套写法中的样式与没有嵌套的版本具有相同的优先级。
提示
尽量避免让选择器嵌套过深,以免让选择器的优先级远远超出实际需要。
如果往子规则集添加更多规则集,就能将样式嵌套进更深的层中;不过这样一来,就像书写更长的选择器一样,最终组合选择器的长度也会相应增大,从而导致优先级过高。建议将规则集的嵌套深度控制在三层以内。如果超过三层,可能就需要考虑重新组织一下代码了。下一节将深入探讨这个问题。
8.4.1 嵌套选择器的使用 Using the nesting selector
除了默认的从属关系外,嵌套还可以用于表示其他结构,例如下面这段没有嵌套的样式代码:
.modal {display: none;
}
.modal.is-open {display: block;
}
该样式中,样式类 modal
显然重复了,但如果改为嵌套的写法,其默认行为将与想要的效果大相径庭。具体来说,要是将 .is-open
嵌入 .modal
代码块中,相当于写成了 .modal .is-open
,中间隔了一个空格(即后代选择器);而我们期望的结果应该是 .modal.is-open
。
不过,只要在内部选择器上用一个 &
连接符来指代外部选择器,就能轻松解决这个问题。这里的连接符 &
也被称为 嵌套选择器(nesting selector)。嵌套选择器可以引用外层选择器,并将其合并到当前内层选择器中。再来看下面这个案例,其中内层选择器 &.is-open
等同于 .modal.is-open
,两个样式类选择器间没有空格。
代码清单 8.15 利用 &
来构建一个复合选择器
.modal {display: none;&.is-open { /* 将选中 .modal.is-open */display: block;}
}
若要针对页面元素的多种变体或状态应用不同的样式,嵌套选择器就会非常好用。例如使用 &:hover
作内部选择器,选中悬停状态下的外层元素;或者使用 &::after
来选中外层选择器的伪元素 ::after
。
注意
CSS 中的
&
无法像Sass
那样,通过嵌套选择器拼接出一个新的类名。在Sass
中,父级为.card
的内部选择器&large
会选中一个拼接后的样式类.cardlarge
;而在 CSS 中则会被解析为large.card
,选中一个并不存在的<large class="card">
元素。
&
还可以用于调整选择器顺序,写到嵌套选择器的后面,如代码清单 8.16 所示。在这段样式代码中,嵌套选择器 .homepage &
将选中所有满足 .homepage .card
条件的元素,即位于元素 .homepage
内部的所有 .card
元素。
代码清单 8.16 在嵌套的 CSS 样式中使用 & 符号
.card {padding: 1rem;border-radius: 0.5rem;background-color: #fff;.homepage & { /* 选中 .homepage .card */background-color: #ccc;}
}
这种写法某种意义上实现了代码的反转(flips the code around):不必再为外层选择器的子元素设计样式,而是在特定上下文中直接给外层元素设计样式即可。
警告
通常情况下,应当尽量避免在选择器末尾使用
&
来嵌套样式。虽然并没有明令禁止这么写,但根据上下文来设计样式不利于样式代码的长期维护。具体原因留待下一章解释,届时会给出其他变通方案。
嵌套选择器一个废弃的早期写法
在嵌套规范的早期版本中,嵌套选择器必须以
&
、.
或者#
符号开头,并且明令禁止以字母标识符开头(即标签选择器),因此,当时写成下列代码这样都是 无效的:.card {h3 { … } }
当时之所以加上这一条,是因为浏览器厂商担心嵌套选择器对其语言解析算法的性能有影响,并且在有效鉴别嵌套选择器和普通属性声明的能力方面信心不足。好在后来他们找到了有效的解决方案,该限制也就此作罢,上面的写法才重新成为有效写法。
之所以引出这一段往事,是因为许多早期教程都遵循了这个废弃的限制条件,并且 Chrome 浏览器和 Safari 浏览器的首批实现版本也确实执行了该限制条件。这种情况预计会在本书付梓前后有所改观。在此期间,可以在内部选择器上添加一个
&
来绕开这一废弃的规定(即写作& h3
)。
这些新的配置选项为 CSS 选择器的合理组织提供了更多新思路。具体如何组织是一个复杂的话题。下一章我会就如何以最佳方式组织选择器给出一些实用的建议,顺带提一下应该尽量避开的一些写法。
8.4.2 深入理解嵌套选择器 Understanding the nuances of the nesting selector
嵌套是 CSS 一个特殊的语言特性,因为从技术上讲,它并没有为 CSS 带来语言层面的新功能。这就是所谓的 语法糖(syntactic sugar):仅仅提供了一种让样式的书写更“甜美”(“sweeter”)的写法,使其更易于阅读或表达某些想法;而在底层,这样的写法则会被转换(或称 desugars,即去糖化处理)为不带嵌套的传统样式代码。
关键要明确一点:在使用 &
引用父级选择器时,转换或去糖化处理的本质,是将父级选择器封装到 :is()
伪类函数中。这样一来,外层选择器就可以写成包含多个选择器的列表形式,例如:
button,
input,
textarea,
select {&.invalid {border: 1px solid red;}
}
浏览器最终会将上述嵌套选择器解析为 :is(button, input, textarea, select).invalid
。借助 :is()
来解释嵌套选择器,浏览器可以直接进行相关转换,即使是再复杂的深度嵌套也不例外。
8.4.2.1 嵌套对优先级的影响 Nesting affects specificity
在简单的示例中,嵌套对优先级的影响往往显而易见。之所以强调出来,主要是出于两个原因。一是嵌套写法会直接影响选择器的优先级。由于 :is()
伪类的优先级是由传入参数的最高优先级决定的,嵌套选择器也具有同样的行为特征。如果父级选择器具有较高的优先级,那么任何使用了 &
的选择器也将具有较高的优先级,例如以下示例样式:
input,
#login-form button {&:focus {border-color: var(--brand-color);}
}
此时,浏览器会将嵌套选择器解析为 :is(input, #login-form button):focus
;即便需要匹配的选择器仅仅是 input:focus
那部分元素(其优先级单看为 0, 1, 1
),最终的优先级却始终是 1, 1, 1
。
8.4.2.2 嵌套对目标元素的影响 Nesting affects which elements are targeted
使用嵌套写法的第二个影响是,最终匹配的目标元素可能比预想的要多。考虑以下嵌套选择器:
.card .title {footer & { … }
}
该嵌套选择器用于匹配 footer
元素内 .card
样式类下的 .title
标题,例如以下 HTML 元素:
<footer><div class="card"><h3 class="title">Franklin Running Club</h3></div>
</footer>
不过很容易漏掉的一点,是该选择器还可以选中以下 HTML 标记中的 .title
标题:
<div class="card"><footer><h3 class="title">Franklin Running Club</h3></footer>
</div>
嵌套选择器最终将转换为 footer :is(.card .title)
。满足条件的 .title
既可以是 footer
的子项,也可以是 .card
的子项;而 footer
与 .card
之间的从属关系并不明确,因此它们可以按任意顺序进行嵌套,选择器都会匹配成功。
这种细微差别很容易被忽视,而且很可能过了很长时间才会注意到,从而导致页面样式报错。一旦出现这样的情况,就必须意识到嵌套选择器相关的影响。
8.4.3 媒体查询及其他 @规则 的嵌套 Nesting media queries and other at-rules
嵌套的写法还可以应用到媒体查询 @media
中,并且写起来非常方便,可能是因为我喜欢将相关代码组合在一起的缘故。这样一来,页面各个部分的响应式代码就可以放到一块儿了。
代码清单 8.17 就给出了一个示例版本。注意,嵌套媒体查询内部的样式声明不需要任何嵌套选择器;它们直接对父级选择器生效。这样,选择器只要写好一次,相应的响应式样式代码就会自动生效。
代码清单 8.17 嵌套媒体查询的示例样式代码
h1 {font-family: var(--font-heading);font-size: 2.2rem;/* h1 的中型断点样式 */@media (min-width: 800px) {font-size: 2.6rem;}/* h1 的大型断点样式 */@media (min-width: 1200px) {font-size: 3rem;}
}
与上述样式等效的非嵌套版本如下:
h1 {font-family: var(--font-heading);font-size: 2.2rem;
}
@media (min-width: 800px) {h1 {font-size: 2.6rem;}
}@media (min-width: 1200px) {h1 {font-size: 3rem;}
}
与媒体查询规则类似,也可以对 @layer
及 @supports
等规则启用嵌套写法,如代码清单 8.18 所示。这些规则均对父级选择器 input
生效:
代码清单 8.18 嵌套写法在 @layer 与 @supports 规则中的应用示例
input {@layer base { /* 嵌套图层写法下的 input 样式规则 */font: inherit;border: 2px solid #999;}@layer modules { /* 嵌套图层写法下的 input 样式规则 */padding: 5px 10px;/* 仅在支持 :user-invalid 伪类的浏览器中生效的 input 样式 */@supports selector(:user-invalid) {border-color: green;&:user-invalid {border-color: red;}}}
}
与代码清单 8.17 一样,上述代码演示了不同 @规则 下的样式嵌套写法,但没有声明任何子选择器(child selector)。类似地,这些样式声明也都将对所在的父级选择器生效。
嵌套写法对于另外两个 @规则 —— @scope
和 @container
同样适用,具体用法将在后续章节详细介绍。
8.4.3.1 始终先写非嵌套声明 Always place nonnested declarations before nested blocks
在本节给出的所有示例中,我都将不带嵌套的样式声明放在了嵌套代码块的开头。强烈建议您也这样做,主要原因有两个。
一是浏览器就是根据这个顺序解析样式的。如果将非嵌套的样式声明放到嵌套代码块的后面,浏览器在实际确定层叠规则的源码顺序时会 逆序执行(reverse the order)。
例如,在代码清单 8.19 中,貌似非嵌套的 2.2rem
字号会覆盖嵌套媒体查询中的字号 2.6rem
;然而事实并非如此。当媒体查询条件匹配成功时,在层叠规则中胜出的其实是 2.6rem
字号,感觉就像非嵌套声明(即 2.2rem
)原本就写在媒体查询前面一样。
代码清单 8.19 实际位于嵌套代码块前的非嵌套声明示例代码
h1 {@media (min-width: 800px) {font-size: 2.6rem;}font-size: 2.2rem; /* 浏览器会将该声明移至 @media 代码块上方,从而颠倒源代码顺序 */
}
如果您用过 CSS 预处理工具,就会知道上述代码与预处理工具中的嵌套行为是一致的。
先写非嵌套声明的第二个原因在于,这样做可以让不支持嵌套写法的浏览器先解析这些普通声明。老版本的浏览器在遇到无法解释的嵌套选择器前,可以先解析诸如 h1 { font-size: 2.2rem;
这样的样式声明,并使之生效。这样往往可以更好地实现优雅降级,而不是让页面在老版本浏览器中几乎没有预定样式。
8.5 本章小结 Summary
- 层叠图层可以对 CSS 优先级进行直接干预。与仅使用选择器优先级和源码顺序相比,层叠图层提供了一种更为合理的解决方案来控制 CSS 样式的优先级。
- 将如下六个图层作为组织样式表的实用模板:
reset
重置图层、theme
主题图层、global
全局图层、layout
布局图层、modules
模块图层以及utilities
工具图层。实际应用时可根据具体需求作适当调整。 :is()
伪类可以从给定的一组选择器中进行挑选,有助于简化重复且冗长的选择器的书写。:where()
伪类的行为模式类似于:is()
伪类,但其自身优先级始终为零;:where()
伪类适用于要求选择器始终处于低位运行、以免覆盖掉其他样式的场合。- CSS 的嵌套写法能够在父级选择器内部放置子级选择器或子级 @规则。
关于《CSS in Depth》(中译本书名《深入解析 CSS》)
第 1 版 | 第 2 版 | |
---|---|---|
读者评分 | 原版:4.7(亚马逊);中文版:9.3(豆瓣) | 原版:5.0(亚马逊);中文版:暂无,待出版 |
出版时间 | 原版:2018 年 3 月;中文版:2020 年 4 月 | 原版:2024 年 7 月;中文版:暂无,待出版 |
原价 | 原版:$44.99;中文版:¥139.00 | 原版:$59.99;中文版:暂无,待出版 |
现价 | 原版:$36.49;中文版:¥52.54 起步 | 原版:$52.09;中文版:暂无,待出版 |
原版国内预订 | 起步价 ¥461.00 | 起步价 ¥750.00 |
本专栏为该书第 2 版高分译文专栏,全网首发,精译精校,持续更新,计划今年内完成全书翻译,敬请期待!!!
目前已完结的章节(可进入本专栏查看详情,连载期间完全免费):
- 第一章 层叠、优先级与继承(已完结)
- 1.1 层叠
- 1.2 继承
- 1.3 特殊值
- 1.4 简写属性
- 1.5 CSS 渐进式增强技术
- 1.6 本章小结
- 第二章 相对单位(已完结)
- 2.1 相对单位的威力
- 2.2 em 与 rem
- 2.3 告别像素思维
- 2.4 视口的相对单位
- 2.5 无单位的数值与行高
- 2.6 自定义属性
- 2.7 本章小结
- 第三章 文档流与盒模型(已完结)
- 3.1 常规文档流
- 3.2 盒模型
- 3.3 元素的高度
- 3.4 负的外边距
- 3.5 外边距折叠
- 3.6 容器内的元素间距问题
- 3.7 本章小结
- 第四章 Flexbox 布局(已完结)
- 4.1 Flexbox 布局原理
- 4.2 弹性子元素的大小
- 4.3 弹性布局的方向
- 4.4 对齐、间距等细节处
- 4.5 本章小结
- 第五章 网格布局(已完结)
- 5.1 构建基础网格
- 5.2 网格结构剖析 (上)
- 5.2.1 网格线的编号(下)
- 5.2.2 网格与 Flexbox 配合(下)
- 5.3 两种替代语法
- 5.3.1 命名网格线
- 5.3.2 命名网格区域
- 5.4 显式网格与隐式网格(上)
- 5.4.1 添加变化 (中)
- 5.4.2 让网格元素填满网格轨道(下)
- 5.5 子网格(全新增补内容)
- 5.6 对齐相关的属性
- 5.7 本章小结
- 第六章 定位与堆叠上下文(已完结)
- 6.1 固定定位
- 6.1.1 创建一个固定定位的模态对话框
- 6.1.2 在模态对话框打开时防止屏幕滚动
- 6.1.3 控制定位元素的大小
- 6.2 绝对定位
- 6.2.1 关闭按钮的绝对定位
- 6.2.2 伪元素的定位问题
- 6.3 相对定位
- 6.3.1 创建下拉菜单(上)
- 6.3.2 创建 CSS 三角形(下)
- 6.4 堆叠上下文与 z-index
- 6.4.1 理解渲染过程与堆叠顺序(上)
- 6.4.2 用 z-index 控制堆叠顺序(上)
- 6.4.3 深入理解堆叠上下文(下)
- 6.5 粘性定位
- 6.6 本章小结
- 第七章 响应式设计(已完结)
- 7.1 移动端优先设计原则(上篇)
- 7.1.1 创建移动端菜单(下篇)
- 7.1.2 给视口添加 meta 标签(下篇)
- 7.2 媒体查询(上篇)
- 7.2.1 深入理解媒体查询的类型(上篇)
- 7.2.2 页面断点的添加(中篇)
- 7.2.3 响应式列的添加(下篇)
- 7.3 流式布局
- 7.4 响应式图片
- 7.5 本章小结