CSS的:is、:where和:has伪类选择器如何工作
CSS选择器允许你通过类型、属性或HTML文档中的位置来选择元素。本教程解释了三个新选项-- :is(), :where(), 和 :has()。
选择器通常在样式表中使用。下面的例子定位了所有<p> 段落元素,并将字体重量改为粗体:
p {
font-weight: bold;
}
你也可以在JavaScript中使用选择器来定位DOM节点:
- document.querySelector()返回第一个匹配的HTML元素
- document.querySelectorAll()在一个类似数组的NodeList中返回所有匹配的HTML元素。
伪类选择器根据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;
}

这个例子添加了一个导航链接子菜单指示器,其中包含一个子菜单项的列表。
/* 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;
}

在你跳进你的编辑器和重构你的CSS代码库之前,请注意:has() 是新的,支持比:is() 和:where() 更加有限。它在Safari 15.4+和Chrome 101+的实验性标志后面可用,但它应该在2023年之前广泛使用。
选择器摘要
:is() 和:where() 伪类选择器简化了CSS语法。你将不再需要嵌套和CSS预处理器(尽管这些工具提供了其他的好处,如参数、循环和减化)。
:has() 父选择是相当具有革命性和令人兴奋的。父级选择将迅速流行起来,我们将忘记那段黑暗的日子当它在所有现代浏览器中可用时,我们将发布一个完整的 教程。:has()