CSS :is, :where 和 :has 伪类选择器如何工作

1,335 阅读8分钟

CSS的:is、:where和:has伪类选择器如何工作

CSS选择器允许你通过类型、属性或HTML文档中的位置来选择元素。本教程解释了三个新选项-- :is(), :where(), 和 :has()。

选择器通常在样式表中使用。下面的例子定位了所有<p> 段落元素,并将字体重量改为粗体:

p {
  font-weight: bold;
}

你也可以在JavaScript中使用选择器来定位DOM节点:

伪类选择器根据HTML元素的当前状态来定位它们。也许最知名的是:hover ,它在光标移动到元素上时应用样式,所以它被用来突出可点击的链接和按钮。其他流行的选项包括:

  • :visited: 匹配被访问的链接
  • :target: 匹配一个文档URL所指向的元素
  • :first-child: 瞄准第一个子元素
  • :nth-child:选择特定的子元素
  • :empty匹配一个没有内容或子元素的元素
  • :checked匹配一个已切换的复选框或单选按钮
  • :blank匹配一个空的输入字段
  • :enabled匹配一个已启用的输入字段
  • :disabled匹配一个禁用的输入字段
  • :required匹配一个必填的输入字段
  • :valid匹配一个有效的输入字段
  • :invalid匹配无效的输入字段
  • :playing目标是一个正在播放的音频或视频元素

浏览器最近又收到了三个伪类选择器......

CSS :is 伪类选择器

注意:这最初被指定为:matches():any() ,但:is() 已经成为CSS的标准。

你经常需要对多个元素应用相同的样式。例如,<p> 段落文本默认为黑色,但当它出现在<article><section> 、或<aside> 中时,则为灰色:

/* default black */
p {
  color: #000;
}

/* gray in <article>, <section>, or <aside> */
article p,
section p,
aside p {
  color: #444;
}

这是一个简单的例子,但更复杂的页面将导致更复杂和更冗长的选择器字符串。任何选择器的语法错误都会破坏所有元素的样式。

像Sass这样的CSS预处理程序允许嵌套(这也将出现在本地CSS中)。

article, section, aside {

  p {
    color: #444;
  }

}

这可以创建相同的CSS代码,减少打字工作,并可以防止错误。但是。

  • 在本地嵌套出现之前,你需要一个CSS构建工具。你可能想使用像Sass这样的选项,但这可能给一些开发团队带来麻烦。
  • 嵌套会引起其他问题。构建深度嵌套的选择器很容易变得越来越难读,并输出冗长的CSS。

:is() 浏览器提供了一个原生的CSS解决方案,在所有的现代浏览器(非IE)中都得到了充分的支持

:is(article, section, aside) p {
  color: #444;
}

一个选择器可以包含任意数量的:is() 伪类。例如,下面这个复杂的选择器对所有<h1><h2><p> 元素应用绿色文本颜色,这些元素是<section> 的子节点,该节点的类是.primary.secondary ,并且不是<article> 的第一个子节点。

article section:not(:first-child):is(.primary, .secondary) :is(h1, h2, p) {
  color: green;
}

没有:is() 的同等代码需要六个CSS选择器。

article section.primary:not(:first-child) h1,
article section.primary:not(:first-child) h2,
article section.primary:not(:first-child) p,
article section.secondary:not(:first-child) h1,
article section.secondary:not(:first-child) h2,
article section.secondary:not(:first-child) p {
  color: green;
}

注意,:is() 不能与::before::after 伪元素相匹配,所以这个例子的代码会失败。

/* NOT VALID - selector will not work */
div:is(::before, ::after) {
  display: block;
  content: '';
  width: 1em;
  height: 1em;
  color: blue;
}

CSS :where 伪类选择器

:where() 选择器的语法与 相同,而且:is() 在所有的现代浏览器中也都支持(不包括IE)。它通常会导致相同的造型。比如说。

:where(article, section, aside) p {
  color: #444;
}

区别在于*特异性*。特异性是用于确定哪个CSS选择器应该覆盖所有其他选择器的算法。在下面的例子中,article p 比单独的p 更加具体,所以<article> 内的所有段落元素都将是灰色的。

article p { color: #444; }
p { color: #000; }

:is() 的情况下,特异性是在其参数中发现的最特异的选择器。在:where() 的情况下,特异性为零。

考虑一下下面的CSS。

article p {
  color: black;
}

:is(article, section, aside) p {
  color: red;
}

:where(article, section, aside) p {
  color: blue;
}

让我们把这个CSS应用到下面的HTML中。

<article>
  <p>paragraph text</p>
</article>

段落文本将被染成红色,如下面的CodePen演示中所示。

请看CodePen上SitePoint(@SitePoint
使用:is选择器

:is() 选择器的特异性与article p 相同,但它在样式表中较晚出现,所以文本变成了红色。有必要同时删除article p:is() 选择器来应用蓝色,因为:where() 选择器的特异性比这两个都低。

更多的代码库会使用:is() ,而不是:where() 。然而,:where() 的零特异性对于CSS重设来说是很实用的,它在没有特定样式的情况下应用标准样式的基线。通常情况下,重设会应用一个默认的字体、颜色、边框和边距。

1em 这个CSS重置代码对<h2> 的标题应用了一个顶部边距*,除非*它们是<article> 元素的第一个孩子。

/* CSS reset */
h2 {
  margin-block-start: 1em;
}

article :first-child {
  margin-block-start: 0;
}

试图在样式表的后面设置一个自定义的<h2> 上边距是没有效果的,因为article :first-child 有更高的特殊性。

/* never applied - CSS reset has higher specificity */
h2 {
  margin-block-start: 2em;
}

你可以用更高的特异性选择器来解决这个问题,但这需要更多的代码,而且对其他开发者来说不一定明显。你最终会忘记你为什么需要它。

/* styles now applied */
article h2:first-child {
  margin-block-start: 2em;
}

你也可以通过对每个样式应用!important 来解决这个问题,但*请避免这样做!*它使进一步的样式设计和开发变得相当有挑战性。

/* works but avoid this option! */
h2 {
  margin-block-start: 2em !important;
}

一个更好的选择是在你的CSS重置中采用:where() 的零特异性。

/* reset */
:where(h2) {
  margin-block-start: 1em;
}

:where(article :first-child) {
  margin-block-start: 0;
}

你现在可以覆盖任何CSS重置样式,而不管其特殊性如何;不需要进一步的选择器或!important

/* now works! */
h2 {
  margin-block-start: 2em;
}

CSS :有伪类选择器

:has() 选择器使用类似于:is():where() 的语法,但它针对的是一个包含其他元素的元素。例如,这里的CSS是为任何包含一个或多个<img><section> 标签的<a> 链接添加一个蓝色的、两像素的边框。

/* style the <a> element */
a:has(img, section) {
  border: 2px solid blue;
}

这是几十年来最激动人心的CSS发展开发人员终于有了一种针对父元素的方法

难以捉摸的 "父级选择器 "一直是人们最需要的CSS功能之一,但它给浏览器供应商带来了性能上的麻烦,因此已经有很长一段时间了。简而言之。

  • 浏览器在页面上绘制一个元素时,会对其应用CSS样式。因此,在进一步添加子元素时,整个父元素必须重新绘制。
  • 在JavaScript中添加、删除或修改元素,可能会影响整个页面的样式,直到包围的<body>

假设供应商已经解决了性能问题,引入:has() ,就可以实现过去没有JavaScript就无法实现的可能性。例如,当任何需要的内部字段无效时,你可以设置外部表单<fieldset> 和下面的提交按钮的样式。

/* red border when any required inner field is invalid */
fieldset:has(:required:invalid) {
  border: 3px solid red;
}

/* change submit button style when invalid */
fieldset:has(:required:invalid) + button[type='submit'] {
  opacity: 0.2;
  cursor: not-allowed;
}

Fieldset shown with a red border and submit button disabled

这个例子添加了一个导航链接子菜单指示器,其中包含一个子菜单项的列表。

/* display sub-menu indicator */
nav li:has(ol, ul) a::after {
  display: inlne-block;
  content: ">";
}

<figure> 或者你可以添加调试样式,比如突出显示所有没有内部img 的元素。

/* where's my image?! */
figure:not(:has(img)) {
  border: 3px solid red;
}

Five images in a row, with a red border around the missing one

在你跳进你的编辑器和重构你的CSS代码库之前,请注意:has() 是新的,支持比:is():where() 更加有限。它在Safari 15.4+和Chrome 101+的实验性标志后面可用,但它应该在2023年之前广泛使用。

选择器摘要

:is():where() 伪类选择器简化了CSS语法。你将不再需要嵌套和CSS预处理器(尽管这些工具提供了其他的好处,如参数、循环和减化)。

:has() 父选择是相当具有革命性和令人兴奋的。父级选择将迅速流行起来,我们将忘记那段黑暗的日子当它在所有现代浏览器中可用时,我们将发布一个完整的 教程。:has()