【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》新版封面

《CSS in Depth》新版封面

译者按
因为临时有事耽搁,有段时间没更新专栏了,今天赶紧补上。原来书中的嵌套(nesting)并非是针对层叠图层(cascade layer)而言的,而是 CSS 新推出的一个语法糖(莫非是迫于 Sass 和 Less 的强大攻势?哈哈),还是等学完这一章再来统一更正前面的说法吧。对一个事物的认识就是这样螺旋上升的。一起学起来!

8.4 嵌套 Nesting

嵌套(Nesting) 是 CSS 的一项功能。它可以将一个样式规则放入另一个样式规则内,从而使子规则的选择器与父规则的选择器相对应,既减少了代码冗余,又可以将相关选择器组合到一起。如果之前用过 SassLess 等 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 本章小结
上一篇:Docker Remote API TLS 认证_docker远程接口未授权访问漏洞怎么解决


下一篇:mit6824-06-Raft学习记录01