立即试用 CSS 嵌套

早在去年 12 月,我们发表了一篇文章,详细介绍了 CSS 嵌套的三种不同选项。在文章中,我们解释了选项 3、选项 4 和选项 5 之间的区别,并通过一系列示例演示了它们各自的工作方式。然后我们提出了一个简单的问题:“哪种选项最适合 CSS 的未来?”

Web 开发者对投票做出了明确回应。选项 3 以压倒性优势获胜。

因此,现在 Safari 和 Chrome 都已实现了选项 3。两周前,即 1 月 25 日,CSS 嵌套已默认在 Safari 技术预览版 162 中发布。如果您有 Mac,只需下载并打开 Safari 技术预览版,编写一些嵌套 CSS,即可体验其工作原理!

CSS 嵌套的工作原理

想象一下,您有一些 CSS 希望以更紧凑的方式编写。

.foo {
  color: green;
}
.foo .bar {
  font-size: 1.4rem;
}

借助 CSS 嵌套,您可以这样编写代码:

.foo {
  color: green;
 .bar {
    font-size: 1.4rem;
  }
}

如果您一直在 Sass 中嵌套样式,您会发现这非常熟悉。

然而,与 Sass 不同的是,这种嵌套并非总是有效。由于浏览器解析引擎的限制,您必须确保嵌套选择器(上述示例中的 .bar)始终以符号开头。

注意:此限制已不再适用。Safari 技术预览版 179+ 不再要求嵌套元素选择器以符号开头。但请跳过此部分以了解更多关于嵌套的便利之处。

如果它是类、ID、伪类、伪元素、属性选择器,或者任何以符号开头的选择器 — 您都成功了。例如,所有这些都将正常工作。以下所有嵌套选择器都以符号开头 — . # : [ * + > ~ — 而不是字母。

main {
 .bar { ... }
 #baz { ...}
 :has(p) { ... }
 ::backdrop { ... }
 [lang|="zh"] { ... }
 * { ... }
 + article { ... }
 > p { ... }
 ~ main { ... }
}

然而,有一种选择器是以字母开头的 — 嵌套元素选择器。这个例子将不起作用:

main {
 article { ... }
}

那段代码将失败,因为 article 以字母开头,而不是符号。它将如何失败?就像您将 article 拼写为 atirlce 时那样。依赖该特定选择器的嵌套 CSS 将被简单地忽略。

您有几个选项来解决这个限制。让我们首先看看您最常使用的解决方案。您可以简单地在元素选择器前面放置一个 &,像这样:

main {
 & article { ... }
}

& 向浏览器发出信号:“我希望这个嵌套外部的选择器放置在这里”。通过在任何元素选择器前面使用 &,您就可以成功地使嵌套选择器以符号而不是字母开头。因此,它会起作用。

aside {
 & p { ... }
}

是以下代码的嵌套等价物:

aside p { ... }

& 对于其他用例也非常方便。

想象您有以下未嵌套的代码:

ul {
  padding-left: 1em;
}
.component ul {
  padding-left: 0;
}

您会注意到预期的选择器是 .component ul — 其中 ul 在第二位。

要编写产生这种结果的嵌套规则,您可以这样写:

ul {
  padding-left: 1em;
  .component & {
    padding-left: 0;
  }
}

同样,& 提供了告诉您“我希望嵌套选择器放置在这里”的方式。

当您不希望选择器之间有空格时,它也很方便。例如:

a {
  color: blue;
  &:hover {
    color: lightblue;
  }
}

这样的代码产生与 a:hover { 相同的结果。如果没有 &,您会得到 a :hover { — 注意 a:hover 之间的空格 — 这将导致您的悬停链接样式失效。

但是,如果您有以下未嵌套的代码呢?

ul {
  padding-left: 1em;
}
article ul {
  padding-left: 0;
}

您不应该这样编写嵌套版本:

ul {
  padding-left: 1em;
  & article & {
    padding-left: 0;
  }
}

为什么不呢?因为那实际上会与以下未嵌套的规则表现相同:

ul {
  padding-left: 1em;
}
ul article ul {
  padding-left: 0;
}

ul article ul 中有两个无序列表?不,那不是我们想要的。

那么,我们该怎么做呢?既然我们需要让 article & 以符号开头?

我们可以这样编写代码:

ul {
  padding-left: 1em;
  :is(article) & {
    padding-left: 0;
  }
}

任何选择器都可以被 :is() 伪类包裹,并保持相同的特异性和含义(当它是括号内唯一的选择器时)。将元素选择器放在 :is() 内部,您将获得一个以符号开头的选择器,这符合 CSS 嵌套的要求。

总而言之,CSS 嵌套将像 Sass 一样工作,但有一个新规则:您必须确保嵌套选择器始终以符号开头。

目前正在调查是否可以在不减慢解析引擎速度的情况下放宽此限制。该限制很可能会被移除 — 无论是很快还是几年后。每个人都同意,如果没有这种限制,嵌套会好得多。但我们也一致认为,网页必须立即显示在浏览器窗口中。在渲染开始前增加哪怕是极小的暂停都不是一个选项。

什么是一个选项?您可以随意构建您的嵌套代码。您可以嵌套多层 — 在已嵌套的 CSS 中嵌套 CSS — 达到您想要的任意深度。您可以随意将嵌套与容器查询(Container Queries)、特性查询(Feature Queries)、媒体查询(Media Queries)和/或层叠层(Cascade Layers)混合使用。任何内容都可以嵌套在任何内容中。

今天就尝试 CSS 嵌套,看看您的想法。在 Safari 技术预览版和 Chrome 开发者版(启用“实验性 Web 平台功能”标志后)中测试您的代码,以确保其产生相同的结果。这是发现 bug 的最佳时机 — 在此新功能发布到任何浏览器之前。您可以在 bugs.webkit.orgbugs.chromium.org 报告问题。此外,请关注 Safari 技术预览版接下来几个版本的发布说明。每个版本都可能为我们的 CSS 嵌套实现添加改进,包括支持 CSSOM 或其他更新的努力,以匹配 CSS 工作组可能进行的任何规范更改。

来自多家公司的许多人近五年来一直致力于将嵌套引入 CSS。其语法经过了激烈辩论,就许多不同解决方案的优缺点进行了长时间的讨论。我们希望您会发现结果非常有帮助。