为什么 :is(::before, ::after) 不能工作?

在 CSS 中,:is() 是一个非常实用的函数型伪类,它可以帮助我们简化和合并选择器。很多 Web 开发者都会用它来减少重复代码,让选择器更简洁、更易读。

例如:

CSS 复制代码
button.large,
button.small {
    /* CSS */
}

可以使用 :is() 改写为:

CSS 复制代码
button:is(.large, .small) {
    /* CSS */
}

这样既保持了相同的效果,又让代码更加紧凑。因此,很多人会把 :is() 理解为一种 "合并选择器"的工具。

不过,当你对 :is() 越来越熟悉时,可能会产生一个看起来很合理的想法:既然 :is() 可以合并选择器,那是否也可以用来同时选择元素的伪元素?例如:

CSS 复制代码
button:is(::before, ::after) {
    /* CSS */
}

从直觉上看,这似乎是在说:选择 button 的 ::before 和 ::after 。但实际上,这段代码不会生效 。浏览器会直接忽略它,因为在 CSS 规范中,伪元素是不允许写在 :is() 里的。

这就引出了一个非常有意思的问题:为什么 :is(::before, ::after) 不能工作? 要理解这个问题,我们需要先弄清楚一件事: :is() 在 CSS 选择器中到底是如何工作的。 很多开发者对它的理解,其实和浏览器真正的解析方式并不完全一样。

在接下来的内容中,我们会一步一步拆解:

  • :is() 的真实作用是什么

  • 为什么伪元素不能出现在 :is() 中

  • 为什么 :is(:hover, :focus) 是合法的,而 :is(::before, ::after) 却不是

  • 以及如何正确地编写涉及伪元素的选择器

理解这些规则之后,你不仅能避免一个常见的 CSS 坑,还能更准确地理解 CSS 选择器的工作方式。

:is() 的真实作用是什么?

很多人第一次接触 :is() 时,都会把它理解为一种用来合并选择器的语法糖。例如:

CSS 复制代码
button.large,
button.small {
    /* CSS */
}

可以写成:

CSS 复制代码
button:is(.large, .small) {
    /* CSS */
}

从结果上看,两种写法确实是等价的,因此很容易让人产生一个印象: :is() 的作用就是把多个选择器合并成一个。但实际上,这只是表面现象。

在 CSS 选择器中,:is() 的真实作用并不是"展开选择器",而是为当前元素增加匹配条件 。换句话说,:is() 本身是一个伪类(pseudo-class) 。它的行为和 .class、#id、:hover 这些选择器类似,都是在为元素增加匹配规则。

例如:

CSS 复制代码
button:is(.large, .small) {
    /* CSS */
}

正确的理解方式是:选择所有 既是 button 元素,同时又匹配 :is() 内部条件的元素。也就是:

  • 元素必须是 button

  • 同时具有 .large 或 .small 类

因此,它并不是先被浏览器转换成:

CSS 复制代码
button.large,
button.small {
    /* CSS */
}

再去匹配元素,而是浏览器直接按照选择器规则进行匹配判断。

理解这一点非常重要,因为它会影响我们如何阅读选择器。例如:

CSS 复制代码
:is(.card, .panel) {
    /* CSS */
}

表示:选择任何 匹配 .card 或 .panel 的元素。 而如果写成:

CSS 复制代码
button:is(.card, .panel) {
    /* CSS */
}

含义就变成:选择 既是 button 元素,同时又匹配 .card 或 .panel 的元素。

因此可以总结为一句话: :is() 用来为当前元素增加一组"可选的匹配条件"。 它并不会改变选择器的目标,也不会去"选择"括号里的内容,而只是判断当前元素是否满足其中任意一个条件。

正是因为这个机制,才会导致一个容易让人困惑的现象:伪元素(例如 ::before 和 ::after )不能写在 :is() 里面。

为什么伪元素不能出现在 :is() 中?

理解这个问题的关键,仍然是 :is() 的作用。正如前面提到的,:is() 是一个伪类,它只是为当前元素增加匹配条件 。也就是说,它是在判断:当前元素是否满足括号中的某个选择器。

例如:

CSS 复制代码
button:is(.large, .small) {
    /* CSS */
}

意思是:选择既是 button 元素,同时又匹配 .large 或 .small 的元素。换句话说,:is() 只是用来判断 button 元素是否符合这些条件。

现在再来看这个选择器:

CSS 复制代码
button:is(::before, ::after) {
    /* CSS */
}

如果按照同样的逻辑去阅读,它的含义就变成:选择既是 button 元素,同时又匹配 ::before 或 ::after 的元素。

问题就在这里,元素不可能是伪元素:

  • button 是一个 真实的 DOM 元素

  • ::before 和 ::after 是 伪元素

伪元素并不是独立的节点,而是附属于某个元素生成的内容。因此,一个元素不可能同时既是元素又是伪元素。所以这个选择器的条件永远不可能成立,浏览器也就不会匹配到任何内容。

这也是为什么 :is() 中只允许写普通选择器或伪类 ,而不能写伪元素。例如下面的写法是完全合法的:

CSS 复制代码
button:is(:hover, :focus, :active){
    /* CSS */
}

它的意思是:选择所有 处于 :hover 、 :focus 或 :active 状态的 button 元素 。因为 :hover、:focus、:active 都是伪类,只是描述元素状态,因此可以作为匹配条件使用。

简单来说,可以记住这样一条规则: :is() 用来给元素增加匹配条件,而不是用来选择伪元素。 同样的限制也适用于另外两个伪类 :not() 和 :where()。

温馨提示:有关于 :is() 、:not() 和 :where() 选择更详细的介绍,请移步阅读《CSS 选择器::where() vs. :is()》!

如何正确地编写涉及伪元素的选择器?

既然 :is() 不能包含伪元素,那么当我们需要同时为多个伪元素编写样式时,应该怎么写?最常见、也是最推荐的方法,就是使用逗号分隔的选择器列表。

例如:

CSS 复制代码
button::before,
button::after {
    content: "";
    position: absolute;
}

这种写法虽然稍微长一点,但它清晰、直观,而且完全符合 CSS 规范。

如果多个选择器的主体部分相同,而只有伪元素不同,也通常只能这样写:

CSS 复制代码
.card::before,
.card::after {
    content: "";
    position: absolute;
}

这里实际上是在选择.card::before 和 .card::after。而不是试图把伪元素放进 :is() 中。

不过,我们仍然可以在元素部分使用 :is() ,然后在最后添加伪元素。例如:

CSS 复制代码
:is(button, a)::before {
    content: "";
}

这个选择器的意思是:选择 button 或 a 元素的 ::before 伪元素。同样的思路也可以用于类选择器:

CSS 复制代码
:is(.card, .panel)::after {
    content: "";
}

表示 .card::after 和 .panel::after。

因此,可以记住一个简单的经验法则: :is() 可以用来匹配元素,但伪元素必须写在选择器的最后。 例如:

CSS 复制代码
/* ✅ 正确写法 */
:is(button, a)::before {
    /* CSS */
}

/* ❌ 错误写法 */
button:is(::before, ::after) {
    /* CSS */
}

总结

:is() 是一个非常强大的 CSS 工具,它可以帮助我们减少重复选择器,让代码更简洁。但需要记住两点关键规则:

  • :is() 用于匹配元素条件,而不是选择伪元素

  • 伪元素必须始终写在选择器的最后

理解这一点之后,你不仅可以避免 :is(::before, ::after) 这样的常见错误,还能更深入地理解 CSS 选择器的匹配机制。

相关推荐
回眸&啤酒鸭4 小时前
【回眸】OpenSwarm 多智能体协作系统实战指南
大数据·前端·人工智能
用户69371750013844 小时前
2026,程序员的时代拐点到了
android·前端·后端
大龄秃头程序员4 小时前
一次 iBeacon + BLE 无感解锁方案的实现记录
前端
小兔子5 小时前
Python 的 GIL 与 free-threading:3.13 之后「去 GIL」走到哪一步了
前端
IT_陈寒5 小时前
SpringBoot自动配置差点让我加班到凌晨
前端·人工智能·后端
guslegend6 小时前
脚手架原理与本地调试:从 bin 软链接到 npm link
前端·npm·node.js·脚手架·前端工程化
海码事务所6 小时前
Google Play 新个人开发者账号上架指南:12 人连续 14 天封闭测试怎么做?
前端
田威AI6 小时前
图片内文字翻译的规格:输入输出、保真、自动化、时间与费用
前端·计算机视觉
不可能片场7 小时前
命令行中文变问号 我用环境变量救了场
前端·electron
lerhxx7 小时前
AI 应用中的上下文管理 —— 从"上下文窗口"到"分层压缩"的工程实践
前端·javascript