新支持的现代CSS伪类选择器指南

1,051 阅读11分钟

伪类选择器是以冒号字符":"开始的,根据当前元素的_状态_进行匹配。这个状态可能是相对于文档树而言的,也可能是对状态变化的反应,如:hover:checked

:any-link

虽然定义在选择器第4级,但这个伪类已经有相当长的时间支持跨浏览器了。any-link 伪类将匹配一个锚超链接,只要它有一个href 。它的匹配方式相当于同时匹配:link:visited 。从本质上讲,如果你要添加基本的属性,如color ,你想应用于所有的链接,无论其访问状态如何,这可能会使你的样式减少一个选择器。

:any-link {
  color: blue;
  text-underline-offset: 0.05em;
}

a 关于特异性的一个重要注意点是,即使:any-link 在级联中被置于较低的位置,也会战胜a 作为一个选择器,因为它具有类的特殊性。在下面的例子中,链接将是紫色的。

:any-link {
  color: purple;
}

a {
  color: red;
}

因此,如果你引入:any-link ,请注意你需要在a 的实例上把它作为一个选择器,如果它们将直接竞争特异性。

:focus-visible

我敢打赌,整个网络上最常见的违反可访问性的行为之一是在交互式元素上删除outline ,如链接、按钮和表单输入的:focus 状态。该outline 的主要目的之一是为主要使用键盘导航的用户提供视觉指示。可见的焦点状态是至关重要的,它可以作为这些用户在界面上的寻路工具,并帮助加强什么是互动元素。具体来说,可见的焦点涵盖在WCAG成功标准2.4.11:焦点外观(最小)

:focus-visible 伪类的目的是只在用户代理通过启发式方法确定焦点环应该是可见的时候才显示。换句话说:浏览器会根据输入法、元素的类型和交互的上下文来决定何时应用:focus-visible 。为了测试的目的,通过台式电脑的键盘和鼠标输入,你应该看到:focus-visible ,当你标签到一个交互式元素,但不是当你点击它,除了文本输入和文本区域,应该显示所有焦点输入类型:focus-visible

注意关于更多的细节,请查看:focus-visible 规范的工作草案

最新版本的Firefox和Chromium浏览器现在似乎正在根据规范处理表单输入的:focus-visible ,该规范指出,当:focus-visible 匹配时,UA应该删除:focus 样式。Safari还不支持:focus-visible ,所以我们需要确保包含一个:focus 样式作为后备,以避免删除outline ,以实现无障碍。

给出一个按钮和文本输入,并设置了以下的样式,让我们看看会发生什么。

input:focus,
button:focus {
  outline: 2px solid blue;
  outline-offset: 0.25em;
}

input:focus-visible {
  outline: 2px solid transparent;
  border-color: blue;
}

button:focus:not(:focus-visible) {
  outline: none;
}

button:focus-visible {
  outline: 2px solid transparent;
  box-shadow: 0 0 0 2px #fff, 0 0 0 4px blue;
}

Chromium和Firefox

  • input
    当元素通过鼠标输入被聚焦时,正确地移除:focus 样式,而支持:focus-visible ,导致改变border-color ,在键盘输入时隐藏outline
  • button
    不仅使用:focus-visible ,而且没有额外的button:focus:not(:focus-visible) 规则,即删除:focus 的轮廓,但只允许在键盘输入时可见box-shadow

野生动物园

  • input
    继续只使用:focus 样式
  • button
    这_似乎_部分地尊重了按钮上:focus-visible 的意图,在点击时隐藏了:focus 样式,但在键盘交互时仍然显示:focus 样式。

因此,目前的建议是继续包括:focus 样式,然后逐步加强到使用演示代码所允许的:focus-visible 。这里有一个CodePen供你继续测试。

请看Stephanie Eckles:focus-visible的笔测试应用

:focus-within

:focus-within 伪类在所有的现代浏览器中都有支持,它的作用_几乎_就像一个父选择器,但只针对一个非常特殊的条件。当连接到一个包含元素和一个子元素匹配:focus ,样式可以被添加到包含元素_和/或_容器内的任何其他元素。

使用这种行为的一个实际的改进是,当相关的输入有焦点时,对表单标签进行造型。为了做到这一点,我们把标签和输入包在一个容器里,然后把:focus-within ,同时选择标签。

.form-group:focus-within label {
  color: blue;
}

这样,当输入有焦点时,标签就会变成蓝色。

这个CodePen演示还包括直接在.form-group 容器中添加一个轮廓。

请看Stephanie Eckles:focus-within的笔测试应用

:is()

也被称为 "匹配任何 "的伪类,:is() ,可以接受一个选择器的列表来尝试匹配。例如,你可以在:is(h1, h2, h3) 的选择器下将它们分组,而不是单独列出标题样式。

关于:is() 选择器列表的几个独特行为。

  • 如果列出的选择器是无效的,规则将继续匹配有效的选择器。鉴于:is(-ua-invalid, article, p) ,该规则将匹配articlep
  • 计算出的特异性将等于通过的具有最高特异性的选择器。例如,:is(#id, p) 将具有#id 的特异性 - 1.0.0 - 而:is(p, a) 的特异性将是 0.0.1。

忽略无效的选择器的第一个行为是一个关键的好处。当在一个选择器无效的组中使用其他选择器时,浏览器将抛出整个规则。这在少数情况下会发挥作用,在这些情况下,供应商前缀仍然是必要的,而将有前缀和无前缀的选择器分组会导致规则在所有浏览器中失败。通过:is() ,你可以安全地将这些样式分组,当它们匹配时,它们将被应用,当它们不匹配时,它们将被忽略。

对我来说,像前面提到的那样分组标题样式已经是这个选择器的一大胜利。这也是在应用非关键样式时,我可以放心使用的规则类型,没有后备措施,例如。

:is(h1, h2, h3) {
  line-height: 1.2;
}

:is(h2, h3):not(:first-child) {
  margin-top: 2em;
}

在这个例子中(来自我的项目SmolCSS中的文档样式),对于不支持的浏览器来说,从基础样式中继承更大的line-height ,或者缺少margin-top ,其实并不是一个问题。它仅仅是不太理想。你还不想使用:is() ,因为它是关键的布局样式,如Grid或Flex,它们能显著控制你的界面。

此外,当链到另一个选择器时,你可以测试基础选择器是否与:is() 中的下级选择器匹配。例如,下面的规则只选择文章的直接后裔的段落。通用选择器被用作对p 基本选择器的参考。

p:is(article > *)

为了获得当前最好的支持,如果你想开始使用它,你也要通过包括使用:-webkit-any():matches() 的重复规则来加倍使用样式。记住要制定这些单独的规则,否则即使是支持的浏览器也会把它扔掉的换句话说,包括以下所有的内容。

:matches(h1, h2, h3) { }

:-webkit-any(h1, h2, h3) { }

:is(h1, h2, h3) { }

在这一点上值得一提的是,与较新的选择器本身一起,还有一个@supports 的更新变体,即@supports selector 。这也可以作为@supports not selector

注意目前(在现代浏览器中),只有Safari不支持这个at-rule。

你可以用下面的方法来检查对:is() 的支持,但实际上你会失去对Safari的支持,因为Safari支持:is() 但不支持@supports selector

@supports selector(:is(h1)) {
  :is(h1, h2, h3) {
    line-height: 1.1;
  }
}

:where()

伪类:where() 几乎与:is() 相同,除了一个关键的区别:它将_永远_具有零特异性。这对那些正在建立框架、主题和设计系统的人来说有不可思议的影响。使用:where() ,作者可以设置默认值,下游的开发者可以包括覆盖或扩展,而不会出现特定性冲突。

考虑一下下面这组img 样式。使用:where() ,即使有一个更高的特异性选择器,特异性仍然是零。在下面的例子中,你认为图片的边框会是哪种颜色?

:where(article img:not(:first-child)) {
    border: 5px solid red;
}

:where(article) img {
  border: 5px solid green;
}

img {
  border: 5px solid orange;
}

第一条规则的特异性为零,因为它完全包含在:where() 。所以直接针对第二条规则,第二条规则获胜。引入img 单一元素选择器作为最后一条规则,由于级联,它将会获胜。这是因为它将计算出与:where(article) img 规则相同的特异性,因为:where() 部分并没有增加特异性。

由于零特异性的特点,将:where() 与回退规则一起使用会比较困难,因为这个特点可能是你_想_使用它而不是:is() 的原因。如果你添加了回退规则,由于其本身的性质,这些规则可能会击败:where() 。而且,它比@supports selector更好的整体支持,所以试图用它来制作一个回退规则,不可能提供太多的(如果有的话)收益。基本上,要注意不能正确地为:where() ,并仔细检查你自己的数据,以确定它是否可以安全地开始用于你独特的受众。

你可以用下面的CodePen进一步测试:where() ,它使用上面的img 选择器。

Stephanie Eckles的PenTesting:where() specificity

增强的:not()

基本的:not() 选择器从Internet Explorer 9开始被支持。但是选择器级别4增强了:not() ,允许它接受一个选择器列表,就像:is():where()

下面的规则在支持的浏览器中提供了相同的结果。

article :not(h2):not(h3):not(h4) {
  margin-bottom: 1.5em;
}

article :not(h2, h3, h4) {
  margin-bottom: 1.5em;
}

:not() 接受选择器列表的能力有很大的现代浏览器支持

正如我们在:is() 中看到的那样,增强的:not() 也可以使用* 包含对基础选择器的引用,作为一个后裔。这个CodePen通过选择_不是_ nav 的后裔的链接来展示这种能力。

参见Stephanie Eckles的Pen测试:not()与一个后裔选择器

奖励:前面的演示还包括一个例子,即通过连锁:not():is() 来选择不是h2h3 元素的相邻兄弟姐妹的图片。

建议但 "有风险" --:has()

最后一个伪类是一个非常激动人心的建议,但目前没有任何浏览器实现它,即使是以实验的方式,它是:has() 。事实上,它在选择器第4级编辑草案中被列为 "有风险",这意味着它被认为在完成实施方面有困难,因此它_可能_从建议中被删除。

如果实施的话,:has() 基本上将成为许多CSS人渴望得到的 "父选择器"。它的工作逻辑类似于:focus-within:is() 的组合,带有后裔选择器,你要寻找后裔的存在,但应用的样式将是父元素的。

考虑到以下规则,如果导航包含一个按钮,那么导航将减少顶部和底部的填充。

nav {
  padding: 0.75rem 0.25rem;

nav:has(button) {
  padding-top: 0.25rem;
  padding-bottom: 0.25rem;
}

同样,这一点目前在任何浏览器中都_没有_实现,即使是实验性的--但想想也是很有趣的。Robin Rendle在CSS-Tricks上对这个未来的选择器提供了更多的见解

第三级的荣誉奖。:empty

你可能错过了第三级选择器中的一个有用的伪类::empty ,当一个元素_没有_子元素时,它可以匹配该元素,包括文本节点。

规则p:empty 将匹配<p></p> ,但不匹配<p>Hello</p>

你可以使用:empty 的一个方法是隐藏那些可能是用JavaScript填充的动态内容的占位符的元素。也许你有一个将接收搜索结果的div,当它被填充时,它将有一个边框和一些填充。但由于还没有结果,你不希望它在页面上占用空间。使用:empty ,你可以用隐藏它。

.search-results:empty {
  display: none;
}

你可能会考虑在空状态下添加一个消息,并且很想用一个伪元素和content 。这里的隐患是,消息可能无法提供给辅助技术的用户,而辅助技术在是否能访问content 。换句话说,为了确保 "没有结果 "类型的信息是可访问的,你会想把它作为一个真正的元素来添加,比如一个段落(对于一个隐藏的div来说,aria-label 将不再是可访问的)。

学习选择器的资源

CSS还有很多选择器,包括伪类。这里有几个地方可以让你了解更多可用的东西。