引言
将近20年,CSS终于在所有现代浏览器实现了原生嵌套语法,是时候淘汰less/sass等预处理器了
来自 Web 开发者的呼声
2012年4月13日,CodePen 的联合创始人 Chris Coyier 抱怨 CSS 的类名不支持命名空间,导致要写好多重复的选择器。
2016年2月2日,微软的项目经理 Kenneth Auchenberg 说如果 CSS 支持了变量和嵌套,他将不再使用预处理器。
2016年12月8日,《CSS揭秘》的作者 Lea Verou 调研了使用 CSS 预处理器的首要原因(单选题),有 1838 个人参与了投票,最终并列第一的两个理由是嵌套和变量。她觉得是时候该重新考虑 CSS 原生嵌套的问题了。
2017年7月13日,集设计和开发才能于一身的 UI/UX 自由工作者 Sara Soueidan 说嵌套是她最想要的 CSS 功能。
2017年8月15日,node-inspect 的作者 Jan Olaf Krems 说 cssnext 把嵌套定义成了“明天的 CSS”,但他还是想看到原生的 CSS 嵌套,毕竟 JS 的生态系统已经证明避免“每个人都使用自己的半标准语言”绝对是健康的。
2018年2月23日,Lea Verou 再次发声,说她现在还在用 CSS 预处理器写嵌套,一旦 CSS 支持了原生嵌套,她就果断弃用预处理。
2018年5月25日,postcss-preset-env 的作者 Jonathan Neal 再次提议重新考虑下让 CSS 支持原生嵌套(也就是本文章的切入点),这引来了一波热议。
在嵌套之前,每个选择器都需要单独声明,相互之间没有联系。这会导致重复、样式表冗余和开发体验分散。
示例
嵌套之前的示例:
.nesting {
color: hotpink;
}
.nesting > .is {
color: rebeccapurple;
}
.nesting > .is > .awesome {
color: deeppink;
}
嵌套之后,选择器可以继续编写,并且与之相关的样式规则可以在其中进行分组。
嵌套之后的示例:
.nesting {
color: hotpink;
>.is {
color: rebeccapurple;
>.awesome {
color: deeppink;
}
}
}
嵌套可以帮助开发人员减少重复选择器的需求,同时还可以将相关元素的样式规则放在一起,提高样式与目标HTML匹配的能力。如果在前面的示例中删除了.nesting
组件,您可以删除整个嵌套组,而不是在文件中搜索相关的选择器实例。
嵌套的作用
嵌套可以帮助:
- 组织代码
- 减小文件大小
- 进行重构
嵌套从Chrome 112版本开始提供支持,并且在Safari技术预览版162中也可以尝试使用。
开始使用CSS嵌套
在本文的其余部分,我们将使用以下演示沙箱来帮助您可视化所选择的内容。在默认状态下,没有选择任何内容,所有内容都可见。通过选择不同的形状和大小,您可以练习语法并查看其效果。
沙箱中有圆、三角形和正方形。其中一些是小的,中等的或大的。其他的是蓝色的、粉色的或紫色的。它们都在.demo
容器元素内。以下是您将要选择的HTML元素的预览。
<div class="demo">
<div class="sm triangle pink"></div>
<div class="sm triangle blue"></div>
<div class="square blue"></div>
<div class="sm square pink"></div>
<div class="sm square blue"></div>
<div class="circle pink"></div>
…
</div>
嵌套示例
CSS嵌套允许您在一个选择器的上下文中定义另一个选择器的样式。
.parent {
color: blue;
.child {
color: red;
}
}
在此示例中,.child
类选择器嵌套在.parent
类选择器内部。这意味着嵌套的.child
选择器只会应用于具有.parent
类的元素的子元素。
这个示例也可以使用&
符号来显式表示父类应该放置在哪里。
.parent {
color: blue;
& .child {
color: red;
}
}
这两个示例在功能上是等效的,并且可以通过更多高级示例来进一步理解它们的用法。
选择圆形
对于第一个示例,任务是为演示中的圆形添加淡化和模糊样式。
不使用嵌套,CSS现在的写法:
.demo .circle {
opacity: .25;
filter: blur(25px);
}
使用嵌套,有两种有效的方式:
/* & is explicitly placed in front of .circle */
.demo {
& .circle {
opacity: .25;
filter: blur(25px);
}
}
或者
/* & + " " space is added for you */
.demo {
.circle {
opacity: .25;
filter: blur(25px);
}
}
结果,所有具有.circle
类的元素在.demo
内部被模糊处理,几乎不可见:
选择任何三角形和正方形
这个任务需要选择多个嵌套元素,也称为组选择器。
不使用嵌套,现在的CSS有两种方式:
.demo .triangle,
.demo .square {
opacity: .25;
filter: blur(25px);
}
或者,使用:is()
/* grouped with :is() */
.demo :is(.triangle, .square) {
opacity: .25;
filter: blur(25px);
}
使用嵌套,有两种有效的方式:
.demo {
& .triangle,
& .square {
opacity: .25;
filter: blur(25px);
}
}
或者
.demo {
.triangle, .square {
opacity: .25;
filter: blur(25px);
}
}
这两种嵌
套选项在内部都使用了:is()
。
.demo :is(.triangle, .square) {
opacity: .25;
filter: blur(25px);
}
结果,.demo
内部只剩下.circle
元素:
选择大三角形和大圆形
这个任务需要使用复合选择器,元素必须同时具有两个类才能被选中。
不使用嵌套,现在的CSS写法:
.demo .lg.triangle,
.demo .lg.square {
opacity: .25;
filter: blur(25px);
}
或者
.demo .lg:is(.triangle, .circle) {
opacity: .25;
filter: blur(25px);
}
使用嵌套,有两种有效的方式:
.demo {
.lg.triangle,
.lg.circle {
opacity: .25;
filter: blur(25px);
}
}
或者
.demo {
.lg {
&.triangle,
&.circle {
opacity: .25;
filter: blur(25px);
}
}
}
结果,所有大三角形和大圆形在.demo
内部被隐藏:
使用复合选择器和嵌套的专业提示
&
符号在这里非常有用,因为它明确显示了如何连接嵌套选择器。考虑以下示例:
.demo {
.lg {
.triange,
.circle {
opacity: .25;
filter: blur(25px);
}
}
}
虽然这是一种有效的嵌套方式,但结果可能不符合您的预期。原因是,如果没有&
来指定期望的.lg.triangle, .lg.circle
组合,实际结果将是.lg .triangle, .lg .circle
;后代选择器。
在没有&
的情况下嵌套类始终会生成后代选择器。使用&
符号可以更改结果。
选择除了粉色形状之外的所有形状
这个任务需要使用否定功能伪类,元素不能具有指定的选择器。
不使用嵌套,现在的CSS写法:
.demo :not(.pink) { opacity: .25; filter: blur(25px);}
使用嵌套,有两种有效的方式:
.demo :not(.pink) {
opacity: .25;
filter: blur(25px);
}
或者
.demo {
:not(.pink) {
opacity: .25;
filter: blur(25px);
}
}
结果,所有不是粉色的形状在.demo
内部被隐藏:
使用&
进行精确控制和灵活性
假设您想要选择.demo
元素,并使用:not()
选择器。这时就需要使用&
:
.demo {
&:not() {
...
}
}
这将.demo
和:not()
合并为.demo:not()
,与之前的示例.demo :not()
不同。这一点非常重要,当您想要嵌套:hover
交互时,需要特别注意。
.demo {
&:hover {
/* .demo:hover */
}
:hover {
/* .demo :hover */
}
}
更多嵌套示例
CSS嵌套规范中包含了更多示例。如果您想通过示例了解更多关于语法的内容,该规范涵盖了各种有效和无效的示例。
接下来的几个示例将简要介绍CSS嵌套的更多特性,帮助您了解其广泛的功能。
嵌套@media
在样式表中,如果要修改选择器及其样式的媒体查询条件位于不同的地方,这可能会分散注意力。使用嵌套,您可以将媒体查询条件直接嵌套在上下文中。
为了方便起见,如果嵌套的媒体查询仅修改当前选择器上下文的样式,则可以使用最简化的语法。
.card {
font-size: 1rem;
@media (width >= 1024px) {
font-size: 1.25rem;
}
}
使用&
也可以:
.card {
font-size: 1rem;
@media (width >= 1024px) {
&.large {
font-size: 1.25rem;
}
}
}
这个示例展示了使用&
的扩展语法,同时也针对.large
卡片进行了定位,以展示继续工作的其他嵌套特性。
了解更多关于嵌套@media的内容。
任意嵌套
到目前为止,所有的示例都是在前一个上下文中继续或附加选择器。如果需要,您可以完全更改或重新排列上下文。
.card {
.featured & {
/* .featured .card */
}
}
&
符号表示选择器对象的引用(不是字符串),可以放置在嵌套选择器的任何位置。甚至可以多次放置:
.card {
.featured & & & {
/* .featured .card .card .card */
}
}
虽然这个示例看起来有点没有用,但在某些情况下,能够重复选择器上下文是很方便的。
无效的嵌套示例
在嵌套中,有几种语法场景是无效的,如果您之前使用预处理器进行嵌套,可能会对此感到惊讶。关于有效嵌套语法的速查表可以在本文的理解嵌套解析器部分找到。
嵌套元素标签名
HTML元素目前需要在前面加上&
符号或使用:is()
进行包装。
.card {
h1 {
/* ? h1 does not start with a symbol */
}
}
通过以下语法进行修复:
.card {
& h1 {
/* ✅ now h1 starts with a symbol */
}
/* or */
:is(h1) {
/* ✅ now h1 starts with a symbol */
}
}
嵌套和连接
许多CSS类命名约定依赖于嵌套能够像连接字符串一样连接或附加选择器。但在CSS嵌套中,这种方法是无效的,因为选择器不是字符串,而是对象引用。
.card {
&--header {
/* is not equal to ".card--header" */
}
}
更详细的解释可以在规范中找到。
混合嵌套和声明
考虑以下嵌套的CSS块:
.card {
color: green;
& { color: blue; }
color: red;
}
.card
元素的颜色将是蓝色。
所有混合的样式声明都会被提升到顶部,就好像它们是在嵌套之前编写的一样。更多细节可以在规范中找到。
理解嵌套解析器
要在CSS嵌套中取得最好的效果,我们可以研究解析器在处理嵌套时的工作原理。了解这一点,我们可以自信地嵌套样式,而不必经常查找规则。
首先,最简单的方法是识别触发解析器开始消耗嵌套样式的符号。
& @ : . > ~ + # [ *
这些符号应该看起来很熟悉。其中一些是组合器,一些是选择器。所以最简单的情况是,如果解析器发现您的嵌套选择器,并且它不以这些符号之一开头,它将失败,并错误地消耗您的样式。
![& @ : . > ~ + # *. 嵌套选择器以这些符号之一开头吗?如果是,则有效。如果不是,则无效。
功能检测
有两种非常好的方法来检测CSS嵌套:使用嵌套或使用
@supports
检查
使用嵌套:
html {
.has-nesting {
display: block;
}
.no-nesting {
display: none;
}
}
使用@supports
检查:
@supports not ((nesting: normal)) { /* 在这里使用传统的样式 */}
这两种方法都依赖于嵌套的nesting
功能。在支持的浏览器中,第一个嵌套示例将起作用,而第二个示例将被忽略。在不支持嵌套的浏览器中,情况正好相反。
总结
CSS嵌套使开发者能够以更直观和组织良好的方式编写样式规则。它有助于减少代码重复、提高可读性,并提供更好的维护性和重构能力。
使用嵌套时,确保理解如何正确放置选择器和使用&
符号来连接或附加选择器。此外,熟悉无效的嵌套示例,以避免错误。
在实际使用CSS嵌套时,请记住进行功能检测,并考虑适当的回退或替代方案,以确保在不支持嵌套的浏览器中也能提供一致的体验。
最后,请记住,嵌套应该是有意义的,并且应该提高代码的可读性和可维护性。避免过度使用嵌套,以免引入样式的复杂性和性能问题。使用嵌套时,请遵循一致的命名约定和最佳实践,以确保团队成员可以轻松理解和维护代码。