还记得我们为什么使用SCSS/LESS等CSS预处理器吗?我想很大一部分人是因为变量和嵌套语法。变量让我们可以很方便的去管理整个系统(网站)中的主题;嵌套语法则可以让我们更好的去组织和编写CSS代码,且让我们的样式文件有了最初始的命名空间或者说是作用域的概念(我们可以把某个模块的代码都放在一个唯一的container当中,这个container就是一个作用域)。至于如条件语句/函数这些特性,如果你不是去开发如Bootstrap/Element-ui 等这些库的样式,在常规的项目编程中通常用到的很少。

2016年12月8日,《CSS揭秘》的作者 Lea Verou 调研了使用 CSS 预处理器的首要原因(单选题),有 1838 个人参与了投票,最终并列第一的两个理由是嵌套和变量。她觉得是时候该重新考虑 CSS 原生嵌套的问题了。

2017年CSS变量(也称为自定义属性)的规范被W3C批准,目前几乎所有的现代浏览器都支持CSS变量。而事实上目前几乎所有主流的库都在使用CSS变量来管理主题。本站也在很早之前就使用了CSS变量。

本站使用的CSS变量
本站使用的CSS变量

2023年,W3C为我们带来了CSS嵌套的规范。且截止目前,这一规范已经被诸多浏览器所引入和支持。就我个人而言,我觉得该规范引入的还是晚了点😂。

css-nesting caniuse 2023年8月支持情况
css-nesting caniuse 2023年8月支持情况

CSS嵌套是什么

CSS嵌套模块定义了嵌套选择器的语法,提供了将一个样式规则嵌套在另一个样式规则中的能力,以提高样式表的模块化和可维护性。

在嵌套之前,子选择器都要重复的声明父选择器,层级越深,声明越多。导致的结果就是样式重复,分散且代码里大(浏览器也要多加载一些字节)。

.button {
  color: var(--theme-default);
}

.button.is-theme-primary {
  color: var(--theme-primary);
}

.button.is-theme-success {
  color: var(--theme-success);
}

.button.is-theme-error {
  color: var(--theme-error);
}

而有了嵌套,选择器可以继续并且可以将与其相关的样式规则分组到其中。

.button {
  color: var(--theme-default);
  &.is-theme-primary {
    color: var(--theme-primary);
  }
  &.is-theme-success {
    color: var(--theme-success);
  }
  &.is-theme-error {
    color: var(--theme-error);
  }
}

嵌套可以帮助我们更好的组织样式结构,减少重复选择器和最终文件的体积大小,同时还可以共同定位相关元素的样式规则。

如何使用

上面章节初步演示了CSS嵌套的使用方法,和SCSS等预处理大差不差。
嵌套规则可以使用嵌套选择器(&)来直接引用父规则的匹配元素,或者使用相对选择器语法指定“后代”以外的关系。

必须明确的向解析器声明正在使用嵌套语法,所以嵌套的子元素必须以 & @ : . > ~ + # [ * 这些标识符开头:

CSS嵌套标识符号
CSS嵌套标识符号

直接嵌套子选择器

.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;
}
/* html 这样使用 
<span class="foo">foo</span>
<span class="foo bar">foo bar</span>
*/
<style>
  .button {
    color: #222;
    &.is-theme-primary {
      color: blue;
    }
    &.is-theme-success {
      color: green;
    }
    &.is-theme-error {
      color: red;
    }
  }
</style>
<button class="button">default</button>
<button class="button is-theme-primary">primary</button>
<button class="button is-theme-success">success</button>
<button class="button is-theme-error">error</button>
&和选择器之间有空格
/* --&和选择器之间有空格-- */
.foo {
  color: red;
  & .bar {
    color: blue;
  }
}

/* 相当于 */
.foo {
  color: red;
}
.foo .bar {
  color: blue;
}

Nesting @media

我们直接可以嵌套@media,这样我们就无需分散的去写媒体查询了。

.card {
  font-size: 1rem;

  @media (width >= 1024px) {
    font-size: 1.25rem;
  }
}

⚠️几种特殊用法

兄弟选择器嵌套不具有父子关系
.foo {
  color: red;
  + .bar {
    color: blue;
  }
  ~ .baz {
    color: green;
  }
}

/* 等同于 */
.foo + .bar {color: blue}
.foo + .baz {color: green}

/* html结构等同于
<span class="foo"></span>
<span class="bar"></span>
<span class="baz"></span>
而不是
<div class="foo">
  <span class="bar"></span>
  <span class="baz"></span>
</div>
*/
&符号不必位于选择器的开头
/* 这里父子关系发生了变化 */

.foo {
  color: red;
  .bar & {
    color: blue;
  }
}

/* 等同于 */
.foo { color: red; }
.bar .foo { color: blue; }

/* ---- */
.foo {
  color: red;
  :not(&) {
    color: blue;
  }
}

/* 等同于 */
.foo { color: red; }
:not(.foo) { color: blue; }

/* ---- */
.card {
  .featured & & & {
  }
}

/* 等同于 */
.featured .card .card .card

⚠️这种规则无效

需要特别注意的一点是,无法直接嵌套元素/类型选择器。这一点和SCSS/LESS等预处理器的嵌套语法不同。

.foo { 
  color: red;
  /* 这种如 span, a, div等元素选择器是无效的 */
  span {
    color: blue;
  }
}
<style>
 .foo { 
    color: red;
    /* 这种如 span, a, div等元素选择器是无效的 */
    span {
      color: blue;
    }
  }
</style>
<div class="foo">直接嵌套<span>元素选择器</span>是无效的</div>

如要直接嵌套元素/类型选择器,则可以使用如下方法来实现:

.foo {
  color: red;
  /* 使用 &空格 来连接 */
  & span {
    color: blue;
  }
  /* 使用 :is伪类匹配选择器来连接 */
  :is(span) {
     color: blue;
  }
}
<style>
 .foo { 
    color: red;
    /* 这种如 span, a, div等元素选择器是无效的 */
    & span {
      color: blue;
    }
  }
</style>
<div class="foo">通过<code>&</code>空格<span>来嵌套元素选择器</span>是有效的</div>

与SCSS/LESS等预处理器的异同

css嵌套模块和SCSS/LESS等预处理器的嵌套语法同样都是提供了将一个样式规则嵌套在另一个样式规则中的能力。但它们是不同的:

  • CSS嵌套是由浏览器解析,而不是由CSS预处理器预编译的;
  • CSS嵌套有助于减小CSS文件的大小,而预处理没有这个能力;
  • 嵌套元素选择器和兄弟选择器的规则不同;
  • CSS嵌套不支持BEM写法;

关于BEM写法

不同于SCSS/LESS等预处理器,CSS原生嵌套是不支持BEM写法的。假设我们要实现如下样式:

.button .button__inner {}
.button .button--primary {}

使用SCSS等预处理很好实现,可以为我们在嵌套中省去.button这几个字符:

.button {
  &__inner {}
  &--primary {}
}

而这一点使用CSS原生嵌套是无法实现的,因为选择器不是字符串,而是对象引用,所以实现起来必须写全:

.button {
  .button__inner {}
  .button--primary {}
}

小结

CSS嵌套无疑是对CSS语法的重大增强,它提高了CSS的模块化、可读性和可维护性,它对CSS的几乎每个架构方面都有革命性意义。如今它真的来了,你准备好放弃SCSS/LESS等预处理器转而使用原生CSS嵌套来重构你的CSS吗?

相关文章

参考文档

-- EOF --

本文标题:在原生CSS中使用嵌套

本文链接:https://smohan.net/blog/css-nesting

本站使用「 署名-非商业性使用 4.0 国际 (CC BY-NC 4.0) 」创作共享协议,转载或使用请署名并注明出处。 相关说明 »

更多文章

评论 「 ... 」