原文地址

简介

如果您有关注过这两年的 CSS 发展状态报告2019年2020年)的话,不难发现,在报告中有专门关于 CSS 新特性一项的介绍:
2022/03/24 【2021年你可能不知道的 CSS 特性(上篇)】 - 图1
上图截取 2020 年 CSS 发展状态报告
是不是有种似有相识的感觉,或者有一些又是那么的陌生。对于我而言,这些都不陌生。不过我想说的是上图(或者报告)中提到的只是冰山一角,有很多是没有出现在调查问卷和报告中。比如接下来内容中提到的伪类选择器,内容可见性,容器查询等等。
嗯!先从 CSS 选择器开始!(^_^)

CSS 伪类选择器

CSS 选择发展到今日,可以说是一个庞大的体系了:
2022/03/24 【2021年你可能不知道的 CSS 特性(上篇)】 - 图2
特别声明,上图来自于 @linxz 的博客如果上图看不清楚可以点击这里获取原图
现在描述 CSS 选择器的规范主要有 CSS (指的是 CSS 2.1),后面分为选择器 Level 3Level 4。伪类选择器在这三部分规范中都有出现过,他们是一个递进的过程,特别是在 Level 3Level 4,在原有的基础上新增了不少优秀的选择器。今天要提到的几个现代伪类选择器就是出于 Levele 4:
2022/03/24 【2021年你可能不知道的 CSS 特性(上篇)】 - 图3

:is() 和 :where()

先来看 :is() 和 :where() 。 @Elad Shechter 曾在推特上发了一个关于 :is() 和 :where()选择器的测试题
2022/03/24 【2021年你可能不知道的 CSS 特性(上篇)】 - 图4
如果你是第一次看到这样的测试题,请先自测一下,如果是你,你会选择哪个答案(green,purple,red 还是 blue)?如果你选择的是 purple ,那么要恭喜你,你答对了。
2022/03/24 【2021年你可能不知道的 CSS 特性(上篇)】 - 图5
Demo 地址:codepen.io/airen/full/…
示例中出现了 :is() 和 :where() 两个可能你从未接触过的伪类选择器:

  1. :is(.header, .main) .p {
  2. color: purple
  3. }
  4. :where(.header, .main) .p {
  5. color: red;
  6. }

其实这两个选择器等同于:

  1. .header .p,
  2. .main .p {
  3. color: purple;
  4. }
  5. .header .p,
  6. .main .p {
  7. color: red;
  8. }

他们唯一不同之处,就是选择器权重不同。:where() 的优先级总是为 0 ,但是 :is() 的优先级是由它的选择器列表中优先级最高的选择器决定的。那么上面的示例中,我们可以用下图来清晰的描述他们之间的关系: 2022/03/24 【2021年你可能不知道的 CSS 特性(上篇)】 - 图6
上图地址来自: twitter.com/eladsc/stat…
即,下面三个选择器选中的相同的元素:

  1. .header .p,
  2. .main .p {
  3. // ...
  4. }
  5. :is(.header, .main) .p {
  6. // ...
  7. }
  8. :where(.header, .main) .p {
  9. // ...
  10. }

不同的是他们的权重不同,其中:

  1. .header .p,
  2. .main .p {
  3. // ....
  4. }
  5. :is(.header, .main) .p {
  6. // ...
  7. }

示例中的两种选择器具有相同的权重(即020);其中:

  1. :where(.header, .main) .p {
  2. // ...
  3. }
  4. .p {
  5. // ...
  6. }

上面示例代码中的选择器具有相同权重(即 010): 2022/03/24 【2021年你可能不知道的 CSS 特性(上篇)】 - 图7
:is() 和 :where() 伪类选择器的出现,将会让我们的选择器变得更简洁,比如下成这个 :is() 的示例:

  1. // Level 0
  2. h1 {
  3. font-size: 30px;
  4. }
  5. // Level 1
  6. section h1, article h1, aside h1, nav h1 {
  7. font-size: 25px;
  8. }
  9. // Level 2
  10. section section h1, section article h1, section aside h1, section nav h1,
  11. article section h1, article article h1, article aside h1, article nav h1,
  12. aside section h1, aside article h1, aside aside h1, aside nav h1,
  13. nav section h1, nav article h1, nav aside h1, nav nav h1, {
  14. font-size: 20px;
  15. }

使用 :is() 可以像下面这样来描述:

  1. // Level 0
  2. h1 {
  3. font-size: 30px;
  4. }
  5. // Level 1
  6. :is(section, article, aside, nav) h1 {
  7. font-size: 25px;
  8. }
  9. // Level 2
  10. :is(section, article, aside, nav)
  11. :is(section, article, aside, nav) h1 {
  12. font-size: 20px;
  13. }

:not()【实用】 和 :has()

或许大家平时在开发前端页面的时候,碰到类似下图这样的需求: 2022/03/24 【2021年你可能不知道的 CSS 特性(上篇)】 - 图8
希望最后一张卡片没有margin-bottom (或第一张卡片没有 margin-top)。针对于这样的场景,:not() 选择器就非常有优势。为什么这么说呢?
在没有 :not() 选择器的时候,你可能会想到下面这样的方式:

  1. .card + .card {
  2. margin-top: 20px;
  3. }
  4. // 或
  5. .card {
  6. margin-bottom: 20px;
  7. }
  8. .card.card--last { // 也可能会使用 .card:last-child
  9. margin-bottom: 0;
  10. }

如果换成 :not() 选择器,可以这要来实现:

  1. .card:not(:last-child) {
  2. margin-bottom: 20px
  3. }

效果如下:
2022/03/24 【2021年你可能不知道的 CSS 特性(上篇)】 - 图9
Demo URL: codepen.io/airen/full/…

虽然 CSS 选择器已经非常强大了,但一直以来,在 CSS 中没有从子元素选到父元素的样的选择器(父选择器):
2022/03/24 【2021年你可能不知道的 CSS 特性(上篇)】 - 图10
有人提出希望有一个 :parents() 这样的选择器!
虽然直到目前为止还没有 :parents() 选择器,但庆幸的是,:has() 选择器到来(兼容较低Safari15.4),它可以用来选择父级元素。目前 Igalia 公司正在为 Chrome 实现该选择器,其团队成员 Brian Kardell 新发表的《Can I :has()》文章中对 :has() 选择器进行了详细的阐述。

  1. <section><!-- section 边框颜色是 blue,margin-bottom是 30px -->
  2. <h1>H1 Level Title</h1>
  3. </section>
  4. <section><!-- section 边框颜色是 #09f,margin-bottom是 30px -->
  5. <h1>H2 Level Title</h1>
  6. </section>
  7. <section><!-- section 边框颜色是 red -->
  8. <p>Text Paragraphs</p>
  9. </section>
  10. /* CSS */
  11. // 将匹配含有h1子元素的 section元素
  12. section:has(h1) {
  13. border-color: blue;
  14. }
  15. // 将匹配含有h2子元素的 section元素
  16. section:has(h2) {
  17. border-color: #09f;
  18. }
  19. // 将匹配含有p子元素的 section元素
  20. section:has(p) {
  21. border-color: red;
  22. }
  23. // 将匹配除了含有p子元素的 section元素
  24. section:not(:has(p)) {
  25. margin-bottom: 30px;
  26. }

示例中还演示了 :not() 和 :has() 组合在一起使用的,但两者组合在一起所达到的意思却完全不一样。其中 :not(:has(selector)) 匹配不含有 selector 元素的父元素,而 :has(:not(selector)) 匹配含有的不是 selector 子元素的元素。两者主要区别在于,:has(:not(selector)) 写法必须要含有一个子元素,而 :not(:has()) 可以不含有元素也会被匹配。
有意思的是,Github 中有一个 Issue 在讨论 **:has-child()** 选择器,或许哪一天,我们在组件中就可以这样使用:
2022/03/24 【2021年你可能不知道的 CSS 特性(上篇)】 - 图11

:empty 和 :blank

在现代 Web 的开发的过程中,总是无法避免数据吐出为空的情况,此时往往会给我们的 UI 带来额外的麻烦。比如说,在同一类的 UI 元件中编写了一定的样式规则,但数据为空,此时在页面上可能会出现这样的场景:
2022/03/24 【2021年你可能不知道的 CSS 特性(上篇)】 - 图12
CSS 的 :empty:blank 两个伪类选择器可以帮助我们避免这种现象。这两个选择器都很有用:

  • 给空元素添加样式
  • 创建空的状态

既然都是可以为空元素添加样式,那何为空元素,他们之间的差异又是什么?先来回答第一个问题,何为空元素?空元素是指元素没有任何子元素或子节点,比如:

  1. <!-- 空元素 -->
  2. <div class="error"></div>
  3. <div class="error"><!-- 注释 --></div>
  4. <!-- 非空元素 -->
  5. <div class="error"> </div><!-- 中间有一个空格符 -->
  6. <div class="error">
  7. </div><!-- 断行 -->
  8. <div class="error">
  9. <!-- 注释 -->
  10. </div><!-- 注释断行排列 -->
  11. <div class="error"><span></span></div>

:empty 和 :blank 相比,:empty 只能选中没有子元素的元素。子元素只可以是元素节点或文本(包括空格)。注释或处理指令都不会产生影响。
2022/03/24 【2021年你可能不知道的 CSS 特性(上篇)】 - 图13
Demo: codepen.io/airen/full/…
注意,在空元素上即使使用伪元素 ::before 或 ::after 创建内容,也能被:empty 识别。
:blank 要比 :empty 灵活地多,只要该元素中无任何子元素都能被识别。不过 W3C 规范对该伪类选择器的定义更趋向于作用到表单控件中,比如用户没有在 input 或 textarea 中输入内容时,提交表单能被识别到。有点类似于表单验证的功能。
早在 2018 年 Zell Liew 在推特上就针对 :empty 和 :blank 做过相关的讨论,并发表一了篇有关于这方面的博文《:empty and :blank》:
2022/03/24 【2021年你可能不知道的 CSS 特性(上篇)】 - 图14
到目前为止,:empty 已得到主流浏览器支持,可以用于实际生产中,但 :blank 伪类选择器还是存在一定的争议的,有关于这方面的讨论可以点击这里查阅

:focus-visible 和 :focus-within

CSSer 对于 :focus 应该不会感到陌生,早期对于可聚焦元素可以使用 :focus 来给元素设置焦点状态下的 UI 风格,即焦点环样式:
2022/03/24 【2021年你可能不知道的 CSS 特性(上篇)】 - 图15
Chrome 86 开始,另外引入了 :focus-visible 和 :focus-within 两种伪类选择器来帮助我们更好的控制 UI 焦点环的样式:
2022/03/24 【2021年你可能不知道的 CSS 特性(上篇)】 - 图16
虽然 :focus 、:focus-within 和 :focus-visible 都可以用来设置焦点环的样式,但他们之间还是有一定的差异:

  • :focus :当用户使用鼠标点击焦点元素或使用键盘的 Tab 键(或快捷键)触发焦点元素焦点环的样式
  • :focus-visible :只有使用键盘的 Tab 键(或快捷键)触发焦点元素焦点环的样式。如果仅使用 :focus-visible 设置焦点环样式的话,那么用户使用鼠标点击焦点元素时不会触发焦点环样式
  • :focus-within:表示一个元素获得焦点,或该元素的后代元素获得焦点。这也意味着,它或它的后代获得焦点,都可以触发 :focus-within

来简单的看一个示例:

  1. button:focus {
  2. outline: 2px dotted #09f;
  3. outline-offset: 2px;
  4. }
  5. button:focus-visible {
  6. outline: 2px solid #f36;
  7. outline-offset: 2px;
  8. }

你会发现,分别使用鼠标点击按钮和按Tab 让按钮获得焦点时焦点环样式效果不同:
2022/03/24 【2021年你可能不知道的 CSS 特性(上篇)】 - 图17
不过需要注意的是,:focus 和 :focus-visible 也会涉及到选择器权重的问题,就上面的示例来说,如果我们把 :focus 选择器对应的样式放置到 :focus-visible 之后:

  1. button:focus-visible {
  2. outline: 2px solid #f36;
  3. outline-offset: 2px;
  4. }
  5. button:focus {
  6. outline: 2px dotted #09f;
  7. outline-offset: 2px;
  8. }

这个时候,你会发现不管用户使用键盘 Tab 键还是鼠标让