likes
comments
collection
share

好物分享 | 🤑这次,has伪类好像真的来了

作者站长头像
站长
· 阅读数 21

未必素娥无怅恨,玉蟾清冷桂花孤。——晏殊·中秋月

前后忙碌月多,整个项目组单独成立分公司,开不完的大会,吃不完的大饼🤮...

终于在中秋节后,一切又归于平静,在下又能网上冲浪,四处摸鱼了👋🐟。

今儿个不写动效(其实是灵感枯竭),今儿个来聊聊一位重磅的、闻名已久的、肥肠好使的css伪类家族成员::has伪类

我们好像在哪见过?

在下相信,跟在下一样到处👋🐟的同学,一定或多或少也听说过这个has伪类,但是好像又从来都没有用过,似曾相识,却素未谋面,朦胧而又神秘。(👂:我们好像在哪儿见过,你记得吗?)

咳咳...

其实说到has伪类,已经不能算是一个新的东西了,has伪类的规范其实制定的很早很早。那到底有多早呢?这么说吧,如果说规范制定算作has伪类的诞生的话,那与在下年纪相仿。

但是为什么一直没有被用起来呢?究其原因,其实是因为浏览器厂商们的担忧——性能消耗。

众所周知,页面DOM的渲染是构建DOM树从上往下渲染的,而这位has伪类,却是通过子元素去匹配对应的父元素,设置父元素的样式,那么势必它就一定要等子元素渲染出来以后才能去进行后续操作,这样一种方式必然会影响网页的渲染速度,从而影响用户体验。

偏偏好巧不巧,has伪类的功能却又非常强大,一定会被大量使用,从而就会导致无法预估的性能消耗,所以在设计之初,has伪类就被明确规定不能用于CSS样式中了。

我曾需要你但你不在

不知道是否只有在下遇到类似这样的问题:子元素样式发生变化,父元素也需要随之而发生变化。

而当时,在下第一反应就是,CSS中有子选择器,那有没有父选择器。查阅各种资料后无果,最终只能通过JS操作dom完成。

(从小就被教育尽量不要直接操作dom元素的在下内心充满了罪孽感...😭)

而这个与在下年纪相仿的has伪类,其实就是在下心心念念的父选择器,满足条件,却不能被使用。

我需要你,但你不在,又是夺摩的悲哀。

2022,我们终将相遇

盼望着,盼望着,它终于来了。

这里有一家公司,它叫Igalia,具体是谁,到底干啥的,咱不需要了解。

咱只要知道,它搞定了一个事情:解决了浏览器几十年来无法解决的性能问题

咋解决的在下不了解,在下只知道,这意味着曾经在下可望不可即的has伪类,它终于要来了!!

就在2022!就在这俩月!九栽锦天!

截止目前(2022/9/14),Chrome105及之后版本、Edge最新版本以及Safari已经完成了对 :has伪类 的支持,而Firefox也开启了实验支持。(快看快看!!)

好物分享 | 🤑这次,has伪类好像真的来了

所以,就照着今年这个浏览器版本迭代的速度,很快在下就可以在项目中愉快的使用父选择器了。

初次见面,请多关照

闲话扯了一堆,今儿个在下就简单介绍一下这位又老又新的朋友——:has()

其实 :has() 的语法比较简单,套用MDN的介绍:

:has代表一个元素,其给定的选择器参数(相对于该元素的 :scope)至少匹配一个元素。

注意,请记住介绍里括号中的内容(:scope

:has() 的使用语法也很简单:

:has( <相对选择器列表> )

他可以接收一个元素,也可以接收一个元素列表,通过接收的参数至少匹配一个元素(当然前提是你得写对)。

接下来就跟随在下去一个个浅尝和测试一下这位新成员的用法。

环境:Chrome 105.0.5195.102(正式版本)(64 位)

.class

class选择器,也是最常用的选择器之一,那我们就从最常用的开刀。

<div class="demo">
    我有一个class为foo的子元素
    <div class="foo"></div>
</div>
<div class="demo">
    我有一个class为bar的子元素
    <div class="bar"></div>
</div>

匹配包含class为foo的元素(即匹配.foo的父元素)。

.demo:has(.foo) {
    background-color: aqua;
}

好物分享 | 🤑这次,has伪类好像真的来了

通过一个简单的尝试,在下成功的实现了对.foo的父元素的选择,但是对.bar的父元素没有进行任何操作,从而实现了父选择器的功能。

咱们接着尝试。

#id

<div class="demo0">
    我有一个id为foo的子元素
    <div id="foo"></div>
</div>
<div class="demo0">
    我有一个id为bar的子元素
    <div id="bar"></div>
</div>

匹配包含id为foo的元素(即匹配#foo的父元素)。

.demo0:has(#foo) {
    background-color: yellowgreen;
}

好物分享 | 🤑这次,has伪类好像真的来了

element

匹配包含span元素的元素(即匹配span的父元素)

<div class="demo1">
    我有span元素
    <span></span>
</div>
<div class='demo1'>
    我没有span元素
</div>
.demo1:has(span) {
    background-color: skyblue;
}

好物分享 | 🤑这次,has伪类好像真的来了

传递一个列表

前面MDN的介绍中提到, :has() 可以接收一个列表,所以在下也试试。

匹配包含class为foo或者包含span元素的父元素(即.foo或者span的父元素)

<div class="demo">
    我有一个class为foo的子元素
    <div class="foo"></div>
</div>
<div class="demo">
    我有一个id为bar的子元素
    <div id="bar"></div>
</div>
<div class="demo">
    我有一个span子元素
    <span></span>
</div>
.demo:has(.foo, span) {
    background-color: blanchedalmond;
}

好物分享 | 🤑这次,has伪类好像真的来了

:has()嵌套

上一个是匹配包含.foo或者span的父元素,那如果想要匹配同时包含.foo和span的父元素怎么操作?

当然是嵌套

<div class="demo">
    我有一个class为foo的子元素
    <div class="foo"></div>
</div>
<div class="demo">
    我有一个span子元素
    <span></span>
</div>
<div class="demo">
    我有一个span子元素和一个class为foo的子元素
    <span></span>
    <div class="foo"></div>
</div>
.demo:has(span):has(.foo) {
    background-color: gold;
}

好物分享 | 🤑这次,has伪类好像真的来了

> element

匹配包含直接class为foo子元素的元素(即.class的直接父元素,非其他爷爷元素)

<div class="demo2">
    我有直接子元素i
    <i></i>
</div>
<div class="demo2">
    我有非直接子元素i
    <span>
        <i></i>
    </span>
</div>
.demo2:has(> i) {
    background-color: orange;
}

好物分享 | 🤑这次,has伪类好像真的来了

注意,匹配直系父元素时,has()里不能用 :has(.demo2 > i) 的形式

原因是前面MDN介绍中提到的:scope,如果用上面这个形式,会找不到.demo2,

所以如果要匹配直系父元素,请使用 :has(> 子元素) 的方式,

现在部分编辑器可能会有报错提示,但是实际上是可以匹配成功的

+ element

相信同学们都用过element + element的选择器,称为兄弟选择器,它是用来匹配紧跟在元素后面的第一个元素。

那么能匹配紧跟在元素前面的第一个元素吗?

:has():当然可以!

<div>
    <div class="demo3">
        我有紧跟的兄弟元素i
    </div>
    <i>
    </i>
</div>
<div>
    <div class="demo3">
        我有不紧跟的兄弟元素i
    </div>
    <span></span>
    <i></i>
</div>
.demo3:has(+ i) {
    background-color: pink;
}

好物分享 | 🤑这次,has伪类好像真的来了

~ element

同理,element ~ element用来匹配元素后面的所有对应兄弟元素,:has() 也可以匹配对应兄弟元素前面的元素

<div>
    <div class="demo4">
        我后面有兄弟span
    </div>
    <i></i>
    <span></span>
</div>
<div>
    <div class="demo4">
        我后面没有兄弟span
    </div>
    <i></i>
    <i></i>
</div>
.demo4:has(~ span) {
    background-color: green;
}

好物分享 | 🤑这次,has伪类好像真的来了

编辑器可能会报错,但是不影响

[attribute]

咱可以通过element[attribute]来匹配一个包含某个属性的元素,反之也可以用 :has() 来匹配一个包含某个拥有attribute属性子元素的父元素。(有点绕...)

匹配包含有name属性的span元素的父元素

<div class="demo7">
    我有子元素span,它有name属性
    <span name="test"></span>
</div>
<div class="demo7">
    我有子元素span,它没有name属性
    <span></span>
</div>
.demo7:has(span[name]) {
    background-color:aqua;
}

好物分享 | 🤑这次,has伪类好像真的来了

其他与匹配属性相关的方法就不列举了。

:hover等其他伪类

同时 :has() 也可以与其他伪类匹配使用,这里在下列举的是 :hover 伪类

匹配子元素被鼠标悬停的得父元素

<div class="demo">
    我是我是按钮1的父元素
    <div class="btn">我是按钮1</div>
</div>
<div class="demo">
    我是我是按钮2的父元素
    <div class="btn">我是按钮2</div>
</div>
.demo:has(.btn:hover) {
    background-color: aquamarine;
}

好物分享 | 🤑这次,has伪类好像真的来了

只是一个简单的分享,为了避免篇幅太长,其他伪类在下也不再赘述了。

往后余生,来日方长

通过跟随在下进行了少量的尝试以后,是否已经对 :has() 有了初步的了解,亦或是已经熟练掌握?

其实 :has() 伪类的用法简单,应用场景却非常之广泛,而其他更具体的灵活使用方法,则需要在漫长的业务代码中各自探索了。

很多同学肯定在想:这不就是个简单的CSS伪类吗,也谈不上功能强大把?

试想一下,当我们 body或者html为最终的父元素,从页面的任何一个元素逐层网上查询,当我们拿到这个最终父元素后,是不是顺其自然的就可以拿到任何一个子元素了?

那么,是不是也就意味着,在处理某一个元素被修改的同时,在八百里开外的某个元素也需要随之修改样式时,我们将不在依赖JS,或者变量传来传去,而是可以直接通过 :has() 伪类进行查找匹配后直接修改😉。


好吧,听起来好像也没有很强大,算了,遇到在聊吧。

好物分享 | 🤑这次,has伪类好像真的来了