关于前端:浅谈逻辑选择器-父选择器它来了

41次阅读

共计 8368 个字符,预计需要花费 21 分钟才能阅读完成。

在 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(header, main, footer) p:hover {
  color: red;
  cursor: pointer;
}

它并没有实现某种选择器的新性能,更像是一种语法糖,相似于 JavaScript ES6 中的 Class() 语法,只是对原有性能的从新封装设计,实现了更容易的表白一个操作的语法,简化了某些简单代码的写法。

语法糖 (syntactic sugar) 是指编程语言中能够更容易的表白一个操作的语法,它能够使程序员更加容易去应用这门语言,操作能够变得更加清晰、不便,或者更加合乎程序员的编程习惯。用比拟通俗易懂的形式去了解就是,在之前的某个语法的根底上扭转了一种写法,实现的性能雷同,然而写法不同了,次要是为了让开发人员在应用过程中更不便易懂。

一图胜前言(援用至 New CSS functional pseudo-class selectors :is() and :where()):

反对多层层叠连用

再来看看这种状况,本来的 CSS 代码如下:

<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(div, p) :is(span, i) {color: red;}

后果如下:

这里,也反对 :is() 的层叠连用。通过 :is(div, p) :is(span, i) 的排列组合,能够组合出上述 4 行的选择器,达到同样的成果。

当然,这个例子比较简单,看不出 :is() 的威力。上面这个例子就比拟显著,这么一大段 CSS 选择器代码:

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 menu, menu ul menu, menu menu menu, menu 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(ol, ul, menu, dir) :is(ol, ul, menu, dir) :is(ul, menu, 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">where & is test</p>
</div>
<div>
    <p class="test-class">where & is test</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 因为优先级不够高的问题,导致第一段文本仍旧是 green

然而,这里,神奇的是,两段文本都变成了 blue

CodePen Demo — the specificity of CSS :is selector

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

对于 div :is(p, #text-id)is:() 外部有一个 id 选择器,因而,被该条规定匹配中的元素,全副都会利用 div #id 这一级别的选择器优先级。这里十分重要,再强调一下,对于 :is() 选择器的优先级,咱们不能把它们割裂开来看,它们是一个整体,优先级取决于 选择器列表中优先级最高的选择器

:is 的别名 :matches() 与 :any()

:is() 是最新的标准命名,在之前,有过有同样性能的抉择,别离是:

:is(div, p) span {}
// 等同于
:-webkit-any(div, p) span {}
:-moz-any(div, p) span {}
:matches(div, p) span {}

当然,上面 3 个都曾经废除,不倡议再持续应用。而到明天(2022-04-27):is() 的兼容性曾经十分不错了,不须要兼容 IE 系列的话能够思考开始用起来(配合 autoprefixer),看看 CanIUse:

:where 伪类选择器

理解了 :is 后,咱们能够再来看看 :where,它们两个有着十分强的关联性。:where 同样是将选择器列表作为其参数,并抉择能够由该列表中的选择器之一抉择的任何元素。

还是这个例子:

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

上述的代码应用了 :where,能够近似的看为:

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

这就有意思了,这不是和下面说的 :is 一样了么?

那么它们的区别在什么中央呢?

:is:where 的区别

首先,从语法上,:is:where 是截然不同的。它们的外围区别点在于 优先级

来看这样一个例子:

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

CSS 代码如下:

:is(div) p {color: red;}
:where(div) p {color: green;}

失常按咱们的了解而言,:is(div) p:where(div) p 都能够转化为 div p,因为 :where(div) p 后定义,所以文字的色彩,应该是 green 绿色,然而,理论的色彩体现为 color: red 红色:

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

上述的例子还不是特地显著,咱们再略微革新下:

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

咱们给 div 增加上一个 id 属性,革新上述 CSS 代码:

:is(div) p {color: red;}
:where(#container) p {color: green;}

即便如此,因为 :where(#container) 的优先级为 0,因而文字的色彩,仍旧为红色 red。:where() 的优先级 总是为 0 这一点在应用的过程中须要牢记。

组合、嵌套

CSS 选择器的一个十分大的特点就在于组合嵌套。:is:where 也不例外,因而,它们也能够相互组合嵌套应用,下述的 CSS 选择器都是正当的:

/* 组合 */
:is(h1,h2) :where(.test-a, .test-b) {text-transform: uppercase;}
/* 嵌套 */
.title:where(h1, h2, :is(.header, .footer)) {font-weight: bold;}

这里简略总结下,:is:where 都是十分好的分组逻辑选择器,惟一的区别在于 :where() 的优先级 总是为 0,而:is() 的优先级是由它的选择器列表中优先级最高的选择器决定的。

:not 伪类选择器

上面咱们介绍一下十分有用的 :not 伪类选择器。

:not 伪类选择器用来匹配不合乎一组选择器的元素。因为它的作用是避免特定的元素被选中,它也被称为反选伪类(negation pseudo-class)。

举个例子,HTML 构造如下:

<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 元素:

MDN 的谬误例子?一个有意思的景象

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

/* Selects any element that is NOT a paragraph */
:not(p) {color: blue;}

意思是,:not(p) 能够抉择任何不是 <p> 标签的元素。然而,下面的 CSS 选择器,在如下的 HTML 构造,实测的后果不太对劲。

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

后果如下:

意思是,:not(p) 依然能够选中 <p> 元素。我尝试了多个浏览器,失去的成果都是统一的。

CodePen Demo — :not pesudo demo

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

咱们把它改成一个不可继承的属性,试试看:

/* Selects any element that is NOT a paragraph */
:not(p) {border: 1px solid;}

OK,这次 <p> 没有边框体现,没有问题!理论应用的时候,须要留神这一层继承的问题!

:not 的优先级问题

上面是一些应用 :not 须要留神的问题。

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

并且,在 CSS Selectors Level 3,:not() 内只反对单个选择器,而从 CSS Selectors Level 4 开始,:not() 外部反对多个选择器,像是这样:

/* CSS Selectors Level 3,:not 外部如果有多个值须要离开 */
p:not(:first-of-type):not(.special) {
}
/* CSS Selectors Level 4 反对应用逗号分隔 */
p:not(:first-of-type, .special) {}

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

:not(*) 问题

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

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

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

禁止套娃。:not 伪类不容许嵌套,这意味着 :not(:not(...)) 是有效的。

:not() 实战解析

那么,:not() 有什么特地有意思的利用场景呢?我这里列举一个。

在 W3 CSS selectors-4 标准 中,新增了一个十分有意思的 :focus-visible 伪类。

:focus-visible 这个选择器能够无效地依据用户的输出形式 (鼠标 vs 键盘) 展现不同模式的焦点。

有了这个伪类,就能够做到,当用户应用鼠标操作可聚焦元素时,不展现 :focus 款式或者让其体现较弱,而当用户应用键盘操作焦点时,利用 :focus-visible,让可获焦元素取得一个较强的体现款式。

看个简略的 Demo:

<button>Test 1</button>
button:active {background: #eee;}
button:focus {outline: 2px solid red;}

应用鼠标点击:

能够看到,应用鼠标点击的时候,触发了元素的 :active 伪类,也触发了 :focus伪类,不太好看。然而如果设置了 outline: none 又会使键盘用户的体验十分蹩脚。因为当键盘用户应用 Tab 尝试切换焦点的时候,会因为 outline: none 而莫衷一是。

因而,能够应用 :focus-visible 伪类革新一下:

button:active {background: #eee;}
button:focus {outline: 2px solid red;}
button:focus:not(:focus-visible) {outline: none;}

看看成果,别离是在鼠标点击 Button 和应用键盘管制焦点点击 Button:

CodePen Demo — :focus-visible example

能够看到,应用鼠标点击,不会触发 :foucs,只有当键盘操作聚焦元素,应用 Tab 切换焦点时,outline: 2px solid red 这段代码才会失效。

这样,咱们就既保证了失常用户的点击体验,也保障了无奈应用鼠标的用户的焦点治理体验,在可拜访性方面下了功夫。

值得注意的是,这里为什么应用了 button:focus:not(:focus-visible) 这么绕的写法而不是间接这样写呢:

button:focus {outline: unset;}
button:focus-visible {outline: 2px solid red;}

解释一下,button:focus:not(:focus-visible) 的意思是,button 元素触发 focus 状态,并且不是通过 focus-visible 触发,了解过去就是在反对 :focus-visible 的浏览器,通过鼠标激活 :focus 的 button 元素,这种状况下,不须要设置 outline

为的是兼容不反对 :focus-visible 的浏览器,当 :focus-visible 不兼容时,还是须要有 :focus 伪类的存在。

因而,这里借助 :not() 伪类,奇妙的实现了一个实用成果的计划降级。

这里有点绕,须要好好了解了解。

:not 兼容性

经验了 CSS Selectors Level 3 & CSS Selectors Level 4 两个版本,到明天(2020-05-04),除去 IE 系列,:not 的兼容性曾经十分之好了:

:has 伪类选择器

OK。最初到所有逻辑选择器外面最重磅的 :has 出场了。它之所以重要是因为它的诞生,填补了在之前 CSS 选择器中,没有外围意义上真正的 父选择器 的空缺。

:has 伪类承受一个选择器组作为参数,该参数绝对于该元素的 :scope 至多匹配一个元素。

理论看个例子:

<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) {border: 1px solid #000;} 

咱们通过 div:has(.g-test-has) 选择器,意思是,抉择 div 下存在 class 为 .g-test-has 的 div 元素。

留神,这里抉择的不是 :has() 内包裹的选择器选中的元素,而是应用 :has() 伪类的宿主元素。

成果如下:

能够看到,因为第二个 div 下存在 class 为 .g-test-has 的元素,因而第二个 div 被加上了 border。

:has() 父选择器 — 嵌套构造的父元素抉择

咱们再通过几个 DEMO 加深下印象。: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-left: 24px;
    border: 1px solid #000;
}

这里,要求精确抉择 div 下间接子元素是 h2,且 h2 下间接子元素有 span 的 div 元素。留神,抉择的最上层应用 :has() 的父元素 div。后果如下:

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

:has() 父选择器 — 同级构造的兄元素抉择

还有一种状况,在之前也比拟难解决,同级构造的兄元素抉择。

看这个 DEMO:

<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-left: 24px;
    border: 1px solid #000;
}

成果如下:

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

这样,始终以来,CSS 没有实现的父选择器,借由 :has() 开始,也可能做到了。这个选择器,可能极大水平的晋升开发体验,解决之前须要比拟多 JavaScript 代码才可能实现的事。

上述 DEMO 汇总,你能够戳这里 CodePen Demo — :has Demo

:has() 兼容性,给工夫一点工夫

比拟惋惜的是,:has() 在最近的 Selectors Level 4 标准中被确定,目前的兼容性还比拟惨淡,截止至 2022-05-04,Safari 和 最新版的 Chrome(V101,可通过开启 Experimental Web Platform features 体验)

Chrome 下开启该个性须要,1. 浏览器 URL 框输出 chrome://flags,2. 开启 #enable-experimental-web-platform-features

急躁期待,给给工夫一点工夫,这么好的选择器马上就能大规模利用了。

最初

本文到此结束,心愿对你有帮忙 :)

想 Get 到最有意思的 CSS 资讯,千万不要错过我的公众号 — iCSS 前端趣闻 😄

<img width=160 src=”https://raw.githubusercontent.com/chokcoco/chokcoco/main/gzh_style.png”>

更多精彩 CSS 技术文章汇总在我的 Github — iCSS,继续更新,欢送点个 star 订阅珍藏。

如果还有什么疑难或者倡议,能够多多交换,原创文章,文笔无限,满腹经纶,文中若有不正之处,万望告知。

正文完
 0