CSS 伪类函数 :where 简介

:where() 是一个非常有用的 CSS 伪类函数 (CSS functional pseudo-class),它允许你将一组选择器打包在一起,并具有一个关键特性:它的特异性(Specificity)总是为零(0)

打开 antd 的开发者模式,发现 antd 就用了不少这样的样式:

看一下他的兼容性,大部分主流浏览器都可以了:

一、:where() 的基本概念

:where() 是一个函数式伪类,它接受一个逗号分隔的选择器列表 作为参数。它会选中任何能被该列表中的任一选择器所匹配的元素。

语法:

css 复制代码
:where(<选择器列表>) {
  /* CSS 样式 */
}

示例:简化选择器 假设你希望同时给 h1h2h3 标签设置一个相同的 margin-top 样式,传统写法是:

css 复制代码
/* 传统写法 */
h1, h2, h3 {
  margin-top: 1.5em;
}

使用 :where() 后,可以写成如下的形式:

css 复制代码
/* 使用 :where() */
:where(h1, h2, h3) {
  margin-top: 1.5em;
}

二、:where() 的核心特性:特异性为零(Specificity: 0)

这是 :where() 与其孪生兄弟 :is() 最大的区别和价值所在。

  • :where() 的特异性: 始终是 0, 0, 0 。无论你传入的内部选择器有多复杂(例如,包含 ID 或类选择器),:where() 本身都不会贡献任何特异性值。
  • :is() 的特异性: 取决于其参数列表中特异性最高的那个选择器

特异性是 CSS 的一个核心概念,它是一个算法,用于确定当多个 CSS 规则试图为同一个元素设置相同的样式属性时,哪个规则应该"获胜"并应用其样式。

简单来说,特异性就是衡量一个 CSS 选择器有多精确的权重值。特异性高的选择器会覆盖特异性低的选择器,即使特异性低的规则在样式表的后面。

特异性(Specificity)决定了在同一个优先级层级(Cascade Layer 和 Origin)

特异性通常以三位数字(或四位,但通常是三位)来计算,例如 (A, B, C)。比较规则是从左到右:一个 ID ((0, 1, 0)) 永远比 100 个类 ((0, 0, 100)) 更具特异性。

特异性对比示例:

选择器 特异性分数 (ID, Class/Pseudo, Element)
h1 0, 0, 1
.main 0, 1, 0
:where(h1, .main) 0, 0, 0
:is(h1, .main) 0, 1, 0 (取 .main 的最高特异性)

!important 的特异性是0,但是!important 优先级位于特异性之上。

但是要避免滥用,因为他会破坏 CSS 级联(Cascade)和特异性(Specificity)的自然流,并且会使得样式难以追溯来源,从而增大调试难度。此外还会限制代码的灵活性和可重用性,别人想要用你的组件,就被迫使用优先级更高的!important来覆盖你的!important。

零特异性的应用场景

零特异性使 :where() 成为定义基础样式重置样式的完美工具。

  1. 基础样式/重置样式 (Baseline Styling): 当你定义一个全局或组件级的默认样式时,你希望它能够被任何其他普通的类选择器轻易覆盖,而不需要引入 !important 或增加额外的特异性。

    css 复制代码
    /* 所有的链接和按钮,默认颜色为蓝色 */
    :where(a, button) {
      color: blue; /* 特异性为 0,0,0 */
    }
    
    /* 此时,一个简单的类就可以轻松覆盖上面的基础样式 */
    .cta-button {
      color: red; /* 特异性为 0,1,0,轻松获胜 */
    }
  2. 避免特异性膨胀: 如果使用 :is(a, button),它的特异性是 0,0,1(取 abutton 的特异性),会增加后续覆盖样式的难度。而 :where() 则完全消除了这种顾虑。

这就是为什么很多组件库,例如 antd 会这样用

三、:where() 的其他优势

1. 简化复合选择器

:where() 可以用在选择器链中的任何位置,极大地简化了复杂的嵌套选择器。

简化前:

css 复制代码
/* 给侧边栏和页脚内的所有链接设置样式 */
.sidebar a,
.footer a {
  text-decoration: none;
}

简化后:

css 复制代码
:where(.sidebar, .footer) a {
  text-decoration: none;
}

2. 容错性(Forgiving Parsing)

与传统的逗号分隔选择器列表不同,:where()(以及 :is())接受一个可容错的选择器列表。

  • 传统列表: 如果列表中的任何一个选择器是无效的(例如,使用了旧浏览器不支持的最新特性),整个规则块都会被忽略

    css 复制代码
    h1, :unsupported-selector { /* 如果 :unsupported-selector 无效,h1 的样式也会失效 */
      color: red;
    }
  • :where()/:is() 列表: 如果列表中的一个选择器是无效的,浏览器只会忽略该无效的选择器 ,而继续使用列表中的其他有效选择器。

    css 复制代码
    :where(h1, :unsupported-selector) { /* 浏览器会忽略 :unsupported-selector,但 h1 的样式仍然有效 */
      color: red;
    }

四、:where():is() 的选择

特性 :where(<选择器列表>) :is(<选择器列表>)
功能 选中列表中任一匹配的元素 选中列表中任一匹配的元素
特异性 始终为零 (0,0,0) 取参数列表中最高特异性
主要用途 定义基础样式重置样式,确保样式易于覆盖。 简化选择器语法,同时保留/提升特异性。

使用场景:

  • 如果你想定义一个容易被覆盖 的默认样式,请使用 :where()
  • 如果你只是想简化 冗长的选择器,并且希望保持提升 特异性(以覆盖其他样式),请使用 :is()

五、在 css module 中使用

在 CSS Modules 中使用 :where() 伪类的方式与普通 CSS 基本一致,但需要注意 CSS Modules 的类名哈希化特性对选择器的影响。以下是具体用法和注意事项:

1. 作用于全局选择器(不被哈希化的类名)

如果需要用 :where() 匹配全局样式类 (未被 CSS Modules 哈希化的类名,通常通过 :global() 声明),直接使用即可:

css 复制代码
/* 组件的 CSS Modules 文件(如 Button.module.css) */
/* 全局类名 .btn-global 不被哈希化 */
:global(.btn-global) {
  padding: 8px;
}

/* 用 :where() 聚合全局选择器 */
:where(:global(.btn-global), :global(.link-global)) {
  color: blue;
}

编译后,:where() 会保留对全局类名的匹配,不受哈希影响。

2. 作用于本地选择器(被哈希化的类名)

如果 :where() 内部包含本地类名 (会被 CSS Modules 哈希化,如 .btn 编译为 btn__123),需要注意:

CSS Modules 会对 :where() 内部的本地类名进行哈希化,但 :where() 的特异性依然生效。

示例:

css 复制代码
/* Button.module.css */
/* 本地类名 .btn 和 .link 会被哈希化 */
.btn {
  border: 1px solid #ccc;
}

.link {
  text-decoration: none;
}

/* 用 :where() 聚合本地类名 */
:where(.btn, .link) {
  font-size: 14px;
}

编译后(假设哈希后缀为 __abc),实际生成的 CSS 为:

css 复制代码
.btn__abc {
  border: 1px solid #ccc;
}

.link__abc {
  text-decoration: none;
}

:where(.btn__abc, .link__abc) {
  font-size: 14px;
}

此时,:where(.btn__abc, .link__abc) 会匹配哈希化后的类名,且特异性为 0。

3. 混合全局和本地选择器

:where() 中可以同时包含全局和本地选择器,CSS Modules 只会哈希化本地类名:

css 复制代码
/* Button.module.css */
.localClass {
  margin: 0;
}

/* 混合全局和本地选择器 */
:where(.localClass, :global(.globalClass)) {
  padding: 0;
}

编译后:

css 复制代码
.localClass__def {
  margin: 0;
}

:where(.localClass__def, .globalClass) {
  padding: 0;
}

完~~

相关推荐
Nick56834 小时前
Swift -- 第三方登录之微信登录 源码分享
前端
麦麦大数据4 小时前
D026 vue3+django 论文知识图谱推荐可视化系统 | vue3+vite前端|neo4j 图数据库
前端·django·vue3·知识图谱·推荐算法·论文文献·科研图谱
小肚肚肚肚肚哦4 小时前
伪元素与普通元素的层级关系问题浅析
前端·css
梦想CAD控件5 小时前
网页CAD中组(Group)功能的二次开发
前端·javascript·github
讨厌吃蛋黄酥5 小时前
🔥 JavaScript异步之谜:单线程如何实现“同时”做多件事?99%的人都理解错了!
前端·javascript·面试
华仔啊5 小时前
别再纠结Pinia和Vuex了!一篇文章彻底搞懂区别与选择
前端·vue.js
徐同保5 小时前
Redux和@reduxjs/toolkit同时在Next.js项目中使用
开发语言·前端·javascript
~无忧花开~5 小时前
CSS学习笔记(二):CSS动画核心属性全解析
开发语言·前端·css·笔记·学习·css3·动画
小九今天不码代码5 小时前
深入理解 CSS 表格布局:table-layout 的秘密与实战详解(附费用报销单案例)
css·前端开发·表格布局·web设计·table-layout·页面优化·样式布局