隔壁小美还不太懂的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 世界。

相关推荐
还是大剑师兰特1 分钟前
面试题:ES6模块与CommonJS模块有什么异同?
前端·es6·大剑师
胡西风_foxww16 分钟前
【ES6复习笔记】数值扩展(16)
前端·笔记·es6·扩展·数值
mosen86818 分钟前
uniapp中uni.scss如何引入页面内或生效
前端·uni-app·scss
白云~️18 分钟前
uniappX 移动端单行/多行文字隐藏显示省略号
开发语言·前端·javascript
沙尘暴炒饭20 分钟前
uniapp 前端解决精度丢失的问题 (后端返回分布式id)
前端·uni-app
昙鱼35 分钟前
springboot创建web项目
java·前端·spring boot·后端·spring·maven
天天进步201540 分钟前
Vue项目重构实践:如何构建可维护的企业级应用
前端·vue.js·重构
小华同学ai43 分钟前
vue-office:Star 4.2k,款支持多种Office文件预览的Vue组件库,一站式Office文件预览方案,真心不错
前端·javascript·vue.js·开源·github·office
APP 肖提莫44 分钟前
MyBatis-Plus分页拦截器,源码的重构(重构total总数的计算逻辑)
java·前端·算法
问道飞鱼1 小时前
【前端知识】强大的js动画组件anime.js
开发语言·前端·javascript·anime.js