逻辑选择器is、where、not、has

249 阅读7分钟

逻辑选择器is、where、not、has

在css选择器中,新增了一类比较新的选择器-逻辑选择器,目前共有4个:

  • :is
  • :where
  • :not
  • :has

:is伪类选择器

:is()CSS伪类函数将选择器列表作为参数,并将给定的样式应用于该列表中的任何元素。

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

header p:hover,  
main p:hover,  
footer p:hover {  
  color: red;  
  cursor: pointer;  
}

使用:is()伪类,上述代码可以改为:

:is(headermainfooterp:hover {  
  color: red;  
  cursor: pointer;  
}   

支持多层层叠连用

<div><i>div i</i></div>  
<p><i>p i</i></p>  
<div><span>div span</span></div>  
<p><span>p span</span></p>  
<h1><span>h1 span</span></h1>  
<h1><i>h1 i</i></h1>

如果将上述HTML中,<div><p>下的<span><i>的color设置为red,正常的css可能是这样:

div span,  
div i,  
p span,  
p i {  
    color: red;  
}

有了:is()后,代码可以简化为:

:is(divp:is(spani) {  
    color: red;  
}

结果如下:

image.png

这里,也支持 :is() 的层叠连用。通过 :is(div, p) :is(span, i) 的排列组合,可以组合出上述 4 行的选择器,达到同样的效果。

复杂的事例

ol ol ul,     ol ul ul,     ol menu ul,     ol dir ul,  
ol ol menu,   ol ul menu,   ol menu menu,   ol dir menu,  
ol ol dir,    ol ul dir,    ol menu dir,    ol dir dir,  
ul ol ul,     ul ul ul,     ul menu ul,     ul dir ul,  
ul ol menu,   ul ul menu,   ul menu menu,   ul dir menu,  
ul ol dir,    ul ul dir,    ul menu dir,    ul dir dir,  
menu ol ul,   menu ul ul,   menu menu ul,   menu dir ul,  
menu ol menumenu ul menumenu menu menumenu dir menu,  
menu ol dir,  menu ul dir,  menu menu dir,  menu dir dir,  
dir ol ul,    dir ul ul,    dir menu ul,    dir dir ul,  
dir ol menu,  dir ul menu,  dir menu menu,  dir dir menu,  
dir ol dir,   dir ul dir,   dir menu dir,   dir dir dir {  
  list-style-type: square;  
}

可以利用 :is() 优化为:

:is(olulmenu, dir) :is(olulmenu, dir) :is(ulmenu, dir) {  
  list-style-type: square;  
}

不支持伪元素

有个特例,不能用 :is() 来选取 ::before 和 ::after 两个伪元素。例如:

  • 注意,仅仅是不支持伪元素,伪类,譬如 :focus:hover 是支持的。
div p::before,  
div p::after {  
    content"";  
    //...  
}

不能写成:

div p:is(::before::after) {  
    content"";  
    //...  
}

:is 选择器的优先级

<div>  
    <p class="test-class" id="test-id">test-class</p>  
</div>  
<div>  
    <p class="test-class">test-class</p>  
</div>

我们给带有 .test-class 的元素,设置一个默认的颜色:

div .test-class {  
    color: red;  
}

这个时候,如果,我们引入 :is() 进行匹配:

div :is(p) {  
    color: blue;  
}

此时,由于 div :is(p) 可以看成 div p,优先级是没有 div .test-class 高的,因此,被选中的文本的颜色是不会发生变化的。

但是,如果,我们在 :is() 选择器中,加上一个 #test-id,情况就不一样了。

div :is(p#text-id) {  
    color: blue;  
}

按照理解,如果把上述选择器拆分,上述代码可以拆分成:

div p {  
    color: blue;  
}  
div #text-id {  
    color: blue;  
}

那么,带有 #text-id 的 <p> 元素由于有了更高优先级的选择器,颜色将会变成 blue,而另外一个 div p 由于优先级不够高的问题,导致第一段文本依旧是 red

但是,这里,两段文本都变成了 blue

image.png

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

:is()兼容性

image.png

:where 伪类选择器

:where 同样是将选择器列表作为其参数,并将给定的样式应用于该列表中的任何元素。 还是这个例子:

header p:hover,  
main p:hover,  
footer p:hover {  
  color: red;  
  cursor: pointer;  
}

上述的代码使用了 :where

:where(headermainfooterp:hover {  
  color: red;  
  cursor: pointer;  
}

:is() 函数的运行方式几乎与 :where() 函数相同

:is 和 :where 的区别

首先,从语法上,:is 和 :where 是一模一样的。它们的核心区别点在于 优先级

<div>  
    <p>where & is test</p>  
</div>

css代码如下

:is(divp {  
    color: red;  
}  
:where(divp {  
    color: green;  
}

正常按我们的理解而言,:is(div) p 和 :where(div) p 都可以转化为 div p,由于 :where(div) p 后定义,所以文字的颜色,应该是 green 绿色,但是,实际的颜色表现为 color: red 红色:

image.png

这是因为,:where() 和 :is() 的不同之处在于,:where() 的优先级总是为 0 ,但是 :is() 的优先级是由它的选择器列表中优先级最高的选择器决定的。

<div id="container">  
    <p>where & is test</p>  
</div>

给 div 添加上一个 id 属性,css代码

:is(divp {  
    color: red;  
}  
:where(#containerp {  
    color: green;  
}

即便如此,由于 :where(#container) 的优先级为 0,因此文字的颜色,依旧为红色 red。

组合、嵌套

CSS 选择器的一个非常大的特点就在于组合嵌套。:is 和 :where 也不例外,因此,它们也可以互相组合嵌套使用,下述的 CSS 选择器都是合理的:

组合

<!-- 组合 -->
  <h1>
    <span class="test-a">h1-test-a</span>
    <span class="test-b">h1-test-b</span>
  </h1>
  <h2>
    <span class="test-a">h2-test-a</span>
    <span class="test-b">h2-test-b</span>
  </h2>
/* 组合*/
    :is(h1,h2) :where(.test-a, .test-b) {
      text-transform: uppercase;
    }

嵌套

 <!-- 嵌套 -->
  <div class="title">
    <h1>
      <span class="header">h1-header</span>
      <span class="footer">h1-footer</span>
    </h1>

    <h2>
      <span class="header">h2-header</span>
      <span class="footer">h1-footer</span>
    </h2>
  </div>
/* 嵌套 */
    .title h1 .header, .title h1 .footer, .title h2 .header, .title h2 .footer{
      font-weight: bold;
      color: red
    }
    .title h1 :where(.header, .footer), .title h2 :where(.header, .footer) {
      font-weight: bold;
      color: red
    }
    .title :where(h1, h2 :is(.header, .footer)) {
      font-weight: bold;
      color: red
    }

:where()兼容性

image.png

:not 伪类选择器

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

<div class="a">div.a</div>  
<div class="b">div.b</div>  
<div class="c">div.c</div>  
<div class="d">div.d</div>
div:not(.b) {  
    color: red;  
}

div:not(.b) 它可以选择除了 class 为 .b 元素之外的所有 div 元素:

image.png

一个有意思的现象

在 MDN 介绍 :not 的页面,有这样一个例子:

:not(p) {  
  color: blue;  
}

:not(p) 可以选择任何不是 <p> 标签的元素。然而,上面的 CSS 选择器,在如下的 HTML 结构,实测的结果不太对。

<p>p</p>  
<div>div</div>  
<span>span</span>  
<h1>h1</h1>

结果如下

image.png

这是为什么呢?

这是由于 :not(p) 同样能够选中 <body>,那么 <body> 的 color 即变成了 blue,由于 color 是一个可继承属性,<p> 标签继承了 <body> 的 color 属性,导致看到的 <p> 也是蓝色。

假如改成一个不可继承的属性:

:not(p) {  
  border1px solid;  
}

image.png

这次 <p> 没有边框体现,没有问题!实际使用的时候,需要注意这一层继承的问题!

:not 的优先级问题

:not:is:where 这几个伪类不像其它伪类,它不会增加选择器的优先级。它的优先级即为它参数选择器的优先级。

与 :is() 类似,:not() 选择器本身不会影响选择器的优先级,它的优先级是由它的选择器列表中优先级最高的选择器决定的。

:not(*) 问题

使用 :not(*) 将匹配任何非元素的元素,因此这个规则将永远不会被应用。

相当于一段没有任何意义的代码。

:not() 不能嵌套 :not()

禁止套娃。:not 伪类不允许嵌套,:not(:not(...)) 是无效的。

:not 兼容性

image.png

:has 伪类选择器

:has 伪类接受一个选择器组作为参数。

 <div>
      <p>div--p</p>
    </div>
    <div>
      <p class="g-test-has">div--p.has</p>
    </div>
    <div>
      <p>div--p</p>
    </div>
div:has(.g-test-has) {  
    border1px solid #000;  
}

 div:has(.g-test-has) 选择器,意思是,选择 div 下存在 class 为 .g-test-has 的 div 元素。 注意,这里选择的不是 :has() 内包裹的选择器选中的元素,而是使用 :has() 伪类的宿主元素。 结果如下

image.png

:has() 父选择器 -- 嵌套结构的父元素选择

<div>  
    <span>div span</span>  
</div>  
  
<div>  
    <ul>  
        <li>  
            <h2><span>div ul li h2 span</span></h2>  
        </li>  
    </ul>  
</div>  
  
<div>  
    <h2><span>div h2 span</span></h2>  
</div>
div:has(>h2>span) {  
    margin-left24px;  
    border1px solid #000;  
}

要求准确选择 div 下直接子元素是 h2,且 h2 下直接子元素有 span 的 div 元素。

image.png

这里体现的是嵌套结构,精确寻找对应的父元素

:has() 父选择器 -- 同级结构的兄元素选择

<div class="has-test">div + p</div>  
<p>p</p>  
  
<div class="has-test">div + h1</div>  
<h1>h1</h1>  
  
<div class="has-test">div + h2</div>  
<h2>h2</h2>  
  
<div class="has-test">div + ul</div>  
<ul>ul</ul>

找到兄弟层级关系中,后面接了 <h2> 元素的  .has-test 元素,可以这样写:

.has-test:has(+ h2) {  
    margin-left24px;  
    border1px solid #000;  
}

效果如下

image.png

这里体现的是兄弟结构,精确寻找对应的前置兄元素

:has() 兼容性

image.png