隔壁小美还不太懂的CSS选择器-:is()、:where()、:not()、:has()

前言

在 CSS 选择器大家族中新增了几个特殊的选择器:

  • :is()
  • :where()
  • :not()
  • :has()

我们称之为**逻辑选择器,**现在很多组件库已经开始使用这类选择器,但是当你看到这些选择器时,是不是一头雾水,这到底是啥意思,选择的又是哪些元素,今天我们就来深入剖析下这几个有意思的选择器。

:is()选择器

基础用法

**:is()** CSS 伪类函数以选择器列表作为参数,并选择该列表中任意一个选择器可以选择的元素。这对于以更紧凑的形式编写大型选择器非常有用。

在之前,对于多个不同父容器的同个子元素的一些共性样式设置,可能会出现如下 CSS 代码:

css 复制代码
.header div:hover,
.main div:hover,
.footer div:hover {
    color: green;
    cursor: pointer;
}

而如今,有了 :is()选择器,则上述代码可以被改写为:

css 复制代码
:is(.header, .main, .footer) div:hover{
    color: green;
    cursor: pointer;
}

可以看到,:is()选择器不像是新语法,更像是一个语法糖,用于简化复杂 CSS 的写法:

在网上找了一张动图,一目了然。

支持多层叠用

考虑下面的代码

html 复制代码
<div>
  <span>div > span</span>
</div>

<div>
  <i>div > i</i>
</div>

<p>
  <span>p > span</span>
</p>

<p>
  <i>p > i</i>
</p>

<p>
  <div>p > div</div>
</p>

<div>
  <p>div > p</p>
</div>

我们希望 div 和 p 标签下的 span 和 i 标签的内容颜色为绿色,CSS 怎么写:

css 复制代码
div span,
div i,
p span, 
p i {
  color: green;
}

现在有了 :is()选择器,代码可以简化为:

css 复制代码
:is(div, p) :is(span, i) {
  color: green;
}

对,:is()选择器可以多次层叠使用,类似于我们高中学的排列组合。

那如果我们希望这些元素是在 hover 或是 focus 状态下才改变字体颜色呢,普通 CSS 怎么写:

css 复制代码
div span:hover,
div i:hover,
p span:hover, 
p i:hover,
div span:focus,
div i:focus,
p span:focus, 
p i:focus{
  color: green;
}

嗯,已经开始有点烦了,那让 :is()选择器来帮我们解决烦恼:

css 复制代码
:is(div, p) :is(span, i):is(:hover, :focus) {
  color: green;
}

好了,瞬间精神了。

:is()选择器的优先级

:is()选择器前面说了只是一个简化的语法糖,所以它的优先级是遵循组合出的 CSS 的选择器的优先级,即ID -> 类 -> 元素的优先级。

考虑如下代码:

html 复制代码
<div>
  <p class="demo-class">元素选择器</p>
</div>

<div>
  <p class="demo-class">类选择器</p>
</div>

<div>
  <p class="demo-class" id="demoId">ID 选择器</p>
</div>
css 复制代码
div :is(p) {
  color: blue;
}

div :is(.demo-class) {
  color: green;
}

很明显,因为类选择器 .demo-class的优先级高于元素选择器 p,所以 p标签的文本颜色为 green 绿色。一起看下结果:

好,一切都在我们的掌控中,考虑下如下 CSS 代码:

css 复制代码
div :is(.demo-class) {
  color: green;
}

div :is(p, #demoId) {
  color: red;
}

将上边的 CSS 拆开来看:

css 复制代码
div .demo-class { color: green }
div p { color: red }
div #demoId { color: red }

根据上边拆分的结果,分析下:

因为 ID 选择器的优先级最高,所以第三个 p 标签文本颜色为红色,而其他两个 p 标签因为都有 .demo-class类,且类选择器优先级高于元素选择器,所以第一,二个 p 标签内容为绿色

但是事实却是:三个 p 标签的文本颜色都是红色,如下所示:

这是由于,:is() 的优先级是由它的选择器列表中优先级最高的选择器决定的。我们不能把它们割裂开来看。

对于 div :is(p, #demoId) 内部有一个 id 选择器,因此,被该条规则匹配中的元素,全部都会应用 div #demoId 这一级别的选择器优先级。

这里非常重要,再强调一下,对于 :is() 选择器的优先级,我们不能把它们割裂开来看,它们是一个整体,优先级取决于选择器列表中优先级最高的选择器

:is()不能选择伪元素

:is()不能选择伪元素,所以下面的代码是行不通的:

css 复制代码
div:is(::after, ::before) {
  display: block;
}

伪元素列表:链接

兼容性

:where()选择器

基础用法

了解了 :is()选择器,接下来我们来看下 :where()选择器。

它们两个之间具有很强的关联性,甚至可以说是双胞胎。还是最开始的例子,改成 :where()选择器

css 复制代码
:where(.header, .main, .footer) div:hover{
    color: green;
    cursor: pointer;
}

上面代码功能类似于如下(意我这里使用的是类似,所以它们是不一样的):

css 复制代码
.header div:hover,
.main div:hover,
.footer div:hover {
    color: green;
    cursor: pointer;
}

你可能会说了,这不跟 :is()选择器一样吗?

那他们之间没有区别吗?

:is():where()的区别

先说结论::is():where()的区别在于::where()优先级总是 0

考虑下面的代码:

html 复制代码
<div>
  <p class="demo-class">where & is</p>
</div>
css 复制代码
div :is(p) {
  color: red;
}

div :where(.demo-class) {
  color: blue;
}

结果:

可以看到,最后文字的展现效果是红色。

分析下:

  • 首先我们使用 :is()选择器定义了 p 元素的文本颜色为红色
  • 之后我们又使用 :where()选择器使用类选择器定义了该 p 标签内的文本颜色为蓝色
  • 结果是红色,很显然,:where()选择器没有生效。

虽然 :where()选择器使用的优先级更高的类选择器,但是依然被低优先级的元素选择器样式覆盖了,这很好的论证了 :where()选择器的优先级为 0 的观点。

除此之外,:is():where()无任何差别。

而且,:is():where()可以嵌套使用,考虑如下代码:

css 复制代码
:is(div, p) :where(span, i) {
  color: red;
}

此代码同时使用了:is():where()实现了嵌套功能,但是因为存在 :where()选择器,所以他们的优先级都为 0;

兼容性

:not()选择器

:not()用来匹配不符合一组选择器的元素。由于它的作用是防止特定的元素被选中,它也被称为_反选伪类_(negation pseudo-class

基础用法

考虑如下代码:

html 复制代码
<div>
  <p class="demo-class1">where & is</p>
  <p class="demo-class2">where & is</p>
  <p class="demo-class3">where & is</p>
  <p class="demo-class4">where & is</p>
  <p class="demo-class5">where & is</p>
  <p class="demo-class6">where & is</p>
</div>
css 复制代码
div p:not(.demo-class1) {
  color: red;
}

根据 :not()选择器器的定义你应该能够理解以上代码的意思:div 下的 p 标签文本的颜色设置为红色,除了那个 class 为 demo-class1的 p 元素,看下结果:

符合预期。

而且,:not()选择器还支持选择器列表为参数进行多选,考虑如下代码:

css 复制代码
div p:not(.demo-class1, .demo-class2) {
  color: red;
}

这样,class 名为 demo-class1demo-class2的元素都不会被选中。

注意点

使用 :not() 时,有几种不寻常的效果和结果需要注意:

  • 可以使用此伪类编写无用的选择器。例如,:not(*) 匹配任何不是元素的元素,这显然是荒谬的,所以这个附加的规则将永远不被应用。
  • 可以利用这个伪类提高规则的优先级。例如,#foo:not(#bar)#foo 都将匹配相同的元素,但是具有两个 id 的选择器具有更高的优先级。
  • :not() 伪类的优先级将由其逗号分割的参数中优先级最高的选择器指定;提供与 :not(:is(argument)) 相同的优先级。
  • :not(.foo) 将匹配任何非 .foo 的元素,包括 html 和 body。
  • 这个选择器将匹配任意"不是一个 X"的元素。当与后代选择器一起使用,这可能令人惊讶,因为有多种路径可以选择一个目标元素。例如,body :not(table) a 仍将应用 table 中的链接,因为 tr、tbody、th、td(en-US)、caption 等都可以匹配选择器 :not(table) 部分。
  • 你可以同时否定多个选择器。例如::not(.foo, .bar) 等同于 :not(.foo):not(.bar)
  • 如果传递给 :not() 伪类的选择器无效或者浏览器不支持,则整个规则都将是无效的。克服这种行为的有效方式是使用::is 伪类,它接受一个可容错选择器列表。例如 :not(.foo, :invalid-pseudo-class) 将使整个规则无效,但是 :not(:is(.foo, :invalid-pseudo-class)) 将匹配任何(包括 html 和 body)不是 .foo 的元素。

兼容性

注意:列表式的选择器和单一的选择器的支持度不一样。

:has()选择器

基础用法

:has()选择器非常有意思,它的出现填补了 CSS 选择器不能选择父元素或是先前兄弟元素的空白。我们一起来看下它是怎么使用的,考虑下面的代码:

html 复制代码
<p><span class="demo">1</span></p>
<p>2</p>
<p><i>3</i></p>
<p><span>4</span></p>
<p>
  <span>
    <span class="demo">5</span>
  </span>
</p>
css 复制代码
p:has(.demo, i) {
  color: red;
}

分析下:

  • html 的结构为五个 p 标签,内部嵌套各不一样,注意最后一个 p 标签内部嵌套了两层
  • css 代码表示,选择当前全部的 p 标签,:has(.demo, i)表示:这全部的 p 标签中我需要最下筛选,其中子元素含有 demo类的和含有 i 标签的筛选出来作为最后的值返回并设置文本颜色为红色。

那么被筛选出来的 p 标签应该是哪个呢?

  • 第一个 p 标签有一个类为 demo的 span 子元素,命中
  • 第二个 p 标签没有子元素,不符合
  • 第三个 p 标签有一个子元素为 i 标签,命中
  • 第四个 p 标签有一个 span标签子元素,但是没有 demo类,不符合
  • 第五个 p 标签有一个嵌套的 span 标签,span 之下又嵌套了一个 span 标签,且这个 span 标签还有 demo类,那这个符合条件吗?先打个问号?

一起看下最后结果:

其中,1 2 5 的 p 标签内容被渲染为红色,意味着这几个命中了规则,很显然,第五个标签也符合条件。

所以总结下 :has()的选择逻辑:

  • 首先必须选择中一些可进行筛选的父级标签
  • 之后 :has()伪类可以搜索这些父级标签下的全部子元素(可以嵌套)或是兄弟元素,执行 :has()内部的选择器,筛选出符合条件的父级标签

嵌套使用

:has()选择器可以跟 :is():where():not()选择器一起使用,实现更搞笑的选择,考虑下面代码:

html 复制代码
<section>
  <article>
    <h1>Morning Times</h1>
    <h2>Delivering you news every morning</h2>
    <p>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
      magna aliqua.
    </p>
  </article>
  <article>
    <h1>Morning Times</h1>
    <h2>Delivering you news every morning</h2>
    <h3>8:00 am</h3>
    <p>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
      magna aliqua.
    </p>
  </article>
</section>
css 复制代码
:is(h1, h2, h3):has(+ :is(h2, h3, h4)) {
  color: red;
}

以上代码使用 :is():has()嵌套选择,表示选择 h1、h2、h3 标签,并且从这些标签中筛选出含有直接兄弟元素为 h2、h3或 h4 的标签。一起看下最后的结果:

快看看和你自己想的结果是不是一致的!!

兼容性

兼容性不容乐观!

总结

本文主要介绍了 :is():where():not():has()选择器的基本使用方法和选择原理并给出了各个选择器的兼容性。

希望本文能帮助你更好的了解奇妙的 CSS 世界。

相关推荐
Ten peaches6 分钟前
Selenium-Java版(环境安装)
java·前端·selenium·自动化
心.c18 分钟前
vue3大事件项目
前端·javascript·vue.js
姜 萌@cnblogs27 分钟前
【实战】深入浅出 Rust 并发:RwLock 与 Mutex 在 Tauri 项目中的实践
前端·ai·rust·tauri
蓝天白云下遛狗35 分钟前
google-Chrome常用插件
前端·chrome
多多*1 小时前
Spring之Bean的初始化 Bean的生命周期 全站式解析
java·开发语言·前端·数据库·后端·spring·servlet
linweidong1 小时前
在企业级应用中,你如何构建一个全面的前端测试策略,包括单元测试、集成测试、端到端测试
前端·selenium·单元测试·集成测试·前端面试·mocha·前端面经
满怀10152 小时前
【HTML 全栈进阶】从语义化到现代 Web 开发实战
前端·html
东锋1.32 小时前
前端动画库 Anime.js 的V4 版本,兼容 Vue、React
前端·javascript·vue.js
满怀10152 小时前
【Flask全栈开发指南】从零构建企业级Web应用
前端·python·flask·后端开发·全栈开发
小杨升级打怪中2 小时前
前端面经-webpack篇--定义、配置、构建流程、 Loader、Tree Shaking、懒加载与预加载、代码分割、 Plugin 机制
前端·webpack·node.js