原文地址: https://blog.logrocket.com/native-css-nesting
原文作者: Sarah Chima Atuonwu
译者:移动应用设计部 – 频道应用研发组 – 孔科翰(kongkehan)
原生 CSS 将要支持 CSS 嵌套啦。这是一个非常好的特性,最重要的一个理由是,它能让我们更容易写出干净、易懂的 CSS 代码。
它是啥样子的呢?让我们先聊聊什么是嵌套,以及它是如何在预处理器中使用的。然后我们将探讨它的一些优点,以及如何在原生 CSS 中使用嵌套。
什么是 CSS 嵌套?
你可能很熟悉这样的 CSS 写法:
.header {
background-color: blue;
}
.header p {
font-size: 16px;
}
.header p span:hover{
color: green;
}
如果用一种 CSS 嵌套的方式来处理这些相同样式:
.header {
background-color: blue;
p {
font-size: 16px;
span {
&:hover {
color: green;
}
}
}
}
这种语法在 SCSS
和 Less
等预处理器中已经得到实现了。从这两段代码中可以看到,嵌套是将一个选择器包含在另一个选择器中。它有助于我们将相关联的样式分组,并在相嵌套的层级结构中编写 CSS。
如果你认为这是不同的,想想你一直在用 HTML
做这样的事情。所以与其一遍又一遍地写相同的选择器名称来给特定的子元素或伪类设置样式,不如将它们直接嵌套在一个选择器下。
嵌套的优点
从上面的片段中,你应该已经感受到了一些使用嵌套的优点。那就再多说两个:
- 编写更加模块化和可维护性更强的样式表。比起将相同的选择器放在同个样式表的多处地方,把所有与该选择器相关联的样式放在同一个地方,会让开发时间和 debug 调试时间变得更快
- 媒体查询也被允许嵌套。有了嵌套以后,就不需要为一个选择器单独添加媒体查询规则,可以直接在定义选择器的地方添加规则
好了,现在的问题是,“ 我们如何在原生 CSS 中使用 CSS 嵌套呢?”
如何在 CSS 中嵌套选择器
CSS 中嵌套法则和我们在上面看到的例子差不太多。根据规范,需要在每个嵌套选择器前面加上 &
符号或者是 @nest
指令 。让我们用原生 CSS 的嵌套方式重写上面的代码:
.header {
background-color: blue;
& p {
font-size: 16px;
& span {
&:hover {
color: green;
}
}
}
}
注意到选择器开头的 &
符号了吗?只有加上它,在 CSS 中的嵌套才会生效。同时 &:hover
则保持了不变,在这种情况下,没有必要额外增加一个 &
。
再举一个例子,如果你想用原生 CSS 嵌套的方式来编写像下方这样的复合选择器:
h1.header {
font-weight: 700;
}
可以这么做:
h1 {
&.header {
font-weight: 700;
}
}
注意,如果你想在同一个元素上为类添加选择器,在 &
和选择器中间不需要有空格。
这是一个媒体查询的嵌套示例:
.header {
font-size: 40px;
@media (max-width: 760px) {
& {
font-size: 24px;
}
}
}
我们可以看到,将相同的样式规则归类在一起真是太有用了。
@nest
指令
虽然用 &
来直接嵌套看起来很完美,但它也有一些有效的嵌套选择器无法处理。让我们来看一个例子:
.header {
background-color: white;
.dark & {
background-color: blue;
}
}
上述的嵌套方式应当是一种合理的写法,但是在 CSS 中 “直接嵌套” 的方式并不能处理当下这种情况,这时就需要用到 @nest
指令了。
@nest
指令让 CSS 中的嵌套变得更加灵活,不需要一定放在选择器的开头,只要在嵌套选择器之中存在一个&
,便可生效,让嵌套的约束变得更少。
上面的例子可以用 @nest
指令来修正:
.header {
background-color: white;
@nest .dark & {
background-color: blue;
}
}
这也等价于:
.header {
background-color: white;
}
.dark .header {
background-color: blue;
}
另一个例子:
.header {
background-color: white;
@nest :not(&) {
background-color: blue;
}
}
等价于:
.header {
background-color: white;
}
:not(.header) {
background-color: blue;
}
注意,为了让 @nest
指令有效,在嵌套选择器中必须存在直接嵌套选择器(&
),不然就无效。所以像下面的示例就是无效的,因为没有存在 &
。
.header {
background-color: white;
@nest .dark {
background-color: blue;
}
}
CSS 的嵌套和权重
在 CSS 中,权重是一套决定哪些样式将被实际应用于元素的规则。如果有两个或以上的选择器被应用于同一个元素,权重最高的那个将会生效。例如下面这个例子:
<html>
<h1 class="header" id="header" />
</html>
<style>
#header {
color: red;
}
.header {
color: blue;
}
h1 {
color: green;
}
</style>
CSS 是层叠的,所以在理想情况下生效的应该是样式表中的最后一个选择器(h1
)。
然而,h1
元素的颜色却是红色的,因为 id
选择器 #header
有着更高的权重优先级。关于权重的更详细的说明,你可以阅读这篇文章 detailed article on specificity 。
这与 CSS 嵌套有何关联呢?让我们看看使用嵌套时浏览器中发生了什么。看下这个例子:
#header, p {
& span {
color: red;
}
}
等价于:
:is(#header, p) span (color: red)
:is
选择器在所有选择器中有着最高的权重,因此很难覆盖它的样式。如果你有一个 p
元素,同时你想改变里面的 span
的颜色,比如像下面这样:
<p class="paragraph">
<span>hey there<span>
</p>
<style>
#header, p {
& span {
color: red;
}
}
.paragraph {
& span {
color: green;
}
}
</style>
颜色将依旧是 red
因为 #header
的权重高于 .paragraph
类。
这对于避免使用非常复杂的选择器或使用 !important
来覆盖样式非常重要。接下来,让我们聊聊一些嵌套时该遵循的常规准则。
常用的嵌套指南
避免过度嵌套
由于嵌套规则使得样式的嵌套变得更加容易,你可能会被诱惑而对选择器进行过度嵌套。看看下面这个例子:
main {
& section {
background-color: red;
& ul {
background-color: green;
& .list {
font-size: 16px;
& .link {
color: pink;
&:hover {
color: blue;
}
}
}
}
}
}
等价于
main section {
background-color: red;
}
main section ul {
background-color: green;
}
main section ul .list{
font-size: 16px;
}
main section ul .list .link{
color: pink;
}
main section ul .list .link:hover{
color: blue;
}
最后一个选择器足足有六层嵌套,在你想对样式进行覆盖的时候,这将会导致许多权重优先级的问题。在一般情况下,建议嵌套的深度控制在三层,你可以使用 stylelint 来控制这种情况。同时尽可能多的使用具有描述性的类名。
嵌套选择器之后的样式将被忽略
使用嵌套的时候,一定要牢牢记住这一点。看看这个例子:
main {
& section {
background-color: red;
}
color: green;
}
color: green
将会被忽略,因为它在嵌套选择器之后。在 CSS 中,任何在嵌套选择器之前的样式才会得到应用。这是 CSS 和其他预处理器在处理嵌套方面的一个区别。
总结
嵌套是一个让人兴奋的特性,它将很快被加入到原生 CSS 中。在本文中我们讨论了如下的内容:
- 嵌套有益于保持样式表的模块化和可维护性。通过嵌套,所有和选择器它本身、它的子/父选择器甚至媒体查询相关的样式都可以放在同一个地方
- 你在什么情况以及何时可以使用直接嵌套的
&
和@nest
指令 - 在使用嵌套时为何要记住权重优先级,并避免过度嵌套
关于 CSS 嵌套的进一步研究,您可以参考 W3C 有关 CSS 嵌套的草案 W3C draft for the CSS nesting module 。