CSS 的父选择器:has()
特别声明:如果您喜欢小站的内容,可以点击申请会员进行全站阅读。如果您对付费阅读有任何建议或想法,欢迎发送邮件至: airenliao@gmail.com!或添加QQ:874472854(^_^)
W3C 的 Selectors Level 4 新增了很多强大的 CSS 选择器。早在 2018 年年底就在《初探CSS 选择器Level 4》一文中和大家一起探讨了这些选择器。在这些新选择器中,最为有意思的是“逻辑组合选择器”,即 “任意匹配伪类选择器:is()
、否定(匹配无)伪类选择器:not()
、选择器权生调整伪类选择器:where()
和关系性(父选择器)伪类选择器:has()
。尤其是关系性伪类选择器:has()
,它和 CSS 容器查询在近十多年来一直成为 Web 开发者期待的 CSS 功能之一。
在这篇文章中,我将和大家一起来探讨什么是关系性伪类选择器(又称父选择器)以及它是如何工作的,并且将会通过一些示例来阐述该选择器可以在哪里,最重要的是我们现在如何使用它。
曾在《初探CSS 选择器Level 4》和《CSS 选择器
:is()
和:where()
与:has()
有什么功能》两篇博文中介绍过新增的逻辑组合选择器,感兴趣的可以先一睹为快!
简单聊一下 CSS 选择器
稍微了解 CSS 的同学都知道,要想给页面添加样式,就得使用 CSS 选择器 来选中 DOM 元素,否则添加的样式就无法运用到具体的元素上。选择器相关的知识是 CSS 领域最基础的部分,但涉及选择器的知识也很多,这一点从 W3C 有关于 选择器规范版本迭代的变更中不难发现(现在已更新到第四版本了,即 Selectors Level 4)。
社区中有关于 CSS 选择器介绍的文章也很多,我个人比较喜欢 nana (@nanacodesign)的 《CSS selectors cheatsheet & details》一文,她用图文并茂的方式介绍了 CSS 选择器常见类型:
小站上也陆续也发布了一些关于 CSS 选择器的文章,摘出一些基础和有意思的文章供大家参考:
- CSS选择器:基本选择器
- CSS选择器:属性选择器
- 再聊CSS的属性选择器
- CSS选择器:伪类选择器
- CSS伪选择器:
:empty
vs:blank
- 初探CSS 选择器Level 4
- CSS 选择器
:is()
和:where()
与:has()
有什么功能 - 编写高效 CSS 选择器
《图解CSS》系列中有关于 CSS 选择器这一章节正在编写之中,感兴趣的可以关注后续相关更新!
CSS选择器简单,而且种类繁多,但一直以来开发者都希望有一个能选中父选择器类型。那么什么是父选择器呢?感兴趣的请继续往下阅读。
父选择器:has()
是什么?
用一句话来描述: :has()
选择器是一个关系型伪类选择器,也被称为函数型伪类选择器,它和 :is()
、:not()
以及 :where()
函数型选择器被称为 CSS的逻辑组合选择器 !
注意,在规范中并没有父选择器一描述,社区中把
:has()
描述为“父选择器”是因为这样更形象,也易于理解。
我们先从其他选择器着手来介绍父选择器是什么?
正如 nana 提供的选择器示例所示,CSS 选择器中有很多类型的选择器是和 DOM 结构有关的,比如我们熟悉的 子选择器(a > b
) 、 后代选择器(a b
) 、 相邻兄弟选择器(a + b
) 、 通用兄弟选择器(a ~ b
) 、 结构伪类选择器(比如 :nth-child()
、:nth-of-type()
等) :
但在这众多的 CSS 选择器中就没有“父选择器”,或许也正因为他的缺失,很多开发者都希望有这样的一个选择器,即,能通过子元素选中其父元素。事实上,社区的开发者从未停止过这方面的探索。比如 Shaun Inman (@shauninman)早在2008年就提出了父选择器的语法规则,即 E < F
。这个语法规则看上去和CSS的子选择器有点相似,只是符号从 >
变成 <
了。
<!-- HTML -->
<a href="##">
<img src="" alt="" />
</a>
/* 子选择器 E > F */
a > img {
border: 2px solid #09f; /* 选中的是子元素 img */
}
/* 父选择器 E < F */
a < img {
border: 2px solid #09f; /* 选中的是父元素 a */
}
之后 Remy Sharp(@rem)建议使用一个 :parent
伪元素来表述父选择器的语法:
a img:parent {
border: 2px solid #09f;
}
稍微熟悉CSS选择器的开发者都知道,选择器最右位才是主体,你要样式化的东西(元素)。大多数编写CSS的人,在某种程度上,发现自己想要基于其中的内容来设计样式。按照这个规则来理解的话,@shauninman 和 @rem 提出的父选择器语法规则都超出我们的认知,比如E < F
选择器的主体是左侧的E
。
后来,Igalia公司的工程师和浏览器内核的工师提出使用 :has()
来定义父选择器的语法规则:
E:has(F) {
}
:has()
选择器看上去和 jQuery 中的:has()
选择器相似。
我们再来看一下W3C规范是怎么描述:has()
选择器:
The relational pseudo-class,
:has()
, is a functional pseudo-class taking a<forgiving-relative-selector-list>
as an argument. It represents an element if any of the relative selectors, when absolutized and evaluated with the element as the:scope
elements, would match at least one element.
大致意思是:
关系型伪类
:has()
是一个函数型伪类,接受一个选择器组(< forgive -relative-selector-list>
)作为参数。其给定的选择器参数(相对于该元素的:scope
)至少匹配一个元素。
其实,我们可以像理解jQuery中的:has()
选择器那样来理解:
:has()
选择器选取所有包含一个或多个元素在其内的元素,匹配指定的选择器。
即,:has()
选择器接受一个相对的选择器列表,如果至少有一个其他元素与列表中的选择器相匹配,那么它将代表一个元素。如果这样不好理解,可以通过下面的示例来理解。假设在我们的 HTML 中有两段这样的代码:
<!-- ① 含有卡片缩略图 img -->
<div class="card">
<img class="card__thumb" src="" alt="" />
<div class="card__content">
<h3 class="card__title">Card Title</h3>
<p class="card__describe">Card Describe</p>
</div>
</div>
<!-- ② 不含卡片缩略图 img -->
<div class="card">
<div class="card__content">
<h3 class="card__title">Card Title</h3>
<p class="card__describe">Card Describe</p>
</div>
</div>
① 和 ② 唯一的区别就是,在 ② 代码片段中不包含卡片缩略图 img
。如果我们在CSS中使用像下面这段CSS代码:
.card {
border-radius: 0.5rem;
box-shadow: 0 0.25rem 0.5rem -0.15rem hsla(0 0% 0% / 55%);
background-color: #fff;
padding: 1em 2em;
min-width: 320px;
}
.card:has(img) {
display: flex;
align-items: center;
gap: 1em;
padding: 0 2em 0 0;
}
在支持:has()
浏览器(写这篇文章为止,你可以在 Safari 15.4 或 Chrome Canary 最新版本)中看到下图这样的效果:
其中:
.card:has(img) {
display: flex;
align-items: center;
gap: 1em;
padding: 0 2em 0 0;
}
上面这段代码表示的是,含有 img
的 .card
元素重置了 .card
的 padding
值,并且添加了 Flexbox 布局相关的样式。换句话说,.card:has(img)
选择器的意思相当于 .card
元素中包含了img
元素吗? 简单地说,在CSS的选择器中有了一个条件判断的逻辑:
if (.card元素包含了img元素) {
.card {
display: flex;
align-items: center;
gap: 1em;
padding: 0 2em 0 0;
}
} else {
.card {
padding: 1em 2em;
}
}
很神奇吧!
父选择器为何会缺失这么久?
父选择器和容器查询特性在近十多年来一直都是众多Web开发者所期待的特性,如果你一直有关注 CSS 状态发展相关的报告,不难发现父选择器和容器查询特性都一直排列前列:
既然“父选择器”众人期待,而且又是那么实用,怎么在 CSS 选择器中一直就没有“父选择器”呢?
正如 Jonathan Snook(@snookca)在2010年的一篇文章中描述的那样,这不仅是因为性能方面的考虑,还因为浏览器引擎渲染文档并将计算样式应用于元素的方式:作为一个流,一个元素接一个元素进入。
上面视频来自于 Ponime 在 YouTube 上发布的视频:Gecko Reflow Visualization - mozilla.org
正如上面视频所演示的那样,当一个元素在浏览器屏幕上渲染出来时,它的父元素以及父元素渲染好的UI样式都已经在那里了。在此之后,重新绘制父节点(以及所有其他父节点)将需要对所有父节点选择器进行另一计算。这样的计算对渲染引擎来说是昂贵的!
曾在《初探 CSS 渲染引擎》和《理解 Web 的重排和重绘》两篇博文中有涉猎过这方面的知识。
之前在整理有关于CSS选择器对性能影响文章(《编写高效 CSS 选择器》)时,发现CSS的通用选择器(*
)是效率最低的CSS选择器。也正如乔纳森.斯努克(Jonathan Snook)说,
如果有一个父选择器,那将很容易成为低效率选择器中的新老大。
其理由是,当从页面中动态地添加和删除元素时,可能会导致整个文档需要重新渲染(主要是内存使用方面的问题),即很容易产生重绘和重排。即使如此,乔纳森.斯努克(Jonathan Snook)仍然很乐观:
我所描述的在技术上并非不可能。事实上,恰恰相反。这只是意味着我们必须处理使用这种特性所带来的性能影响。
这观点后来也得到了 Eric Meyer(@meyerweb)印证,“性能问题可能已经解决了”!Eric Meyer 在 Twitter上发了一条信息,提到了如何避免一直困扰着父选择器给渲染带来的性能问题。
如果对于该话题感兴趣的话,可以直接观看 Byungwoo Lee(@byungwoo_bw)在YouTube发的视频《'has' prototyping status》,视频中对应的 PPT 可以点击这里查阅。Byungwoo Lee 还专门用了两篇文章(《CSS Selectors :has()
:A way of selecting the parent element》和《How blink tests :has()
pseudo class:How to use cache to control :has()
complexity》)介绍 :has()
的使用、存在问题以及如何使用缓存来控制:has()
的复杂性等。
简而言之,浏览器渲染引擎的策略就好像是下象棋一要,应该快速找到如何忽略无关的走法,而不是预测每种走法组合的所有可能结果。对于 CSS 而言,渲染引擎会防止对不相关元素的计算。为了减少应用样式后不相关的重新计算,渲染引擎可以在重新计算期间标记样式是否受到:has()
状态更改的影响。
另外,这几年浏览器的渲染引擎已经有了相当大的改进。渲染过程已经被优化到浏览器可以有效地确定哪些需要渲染或更新,哪些不需要,从而为一系列新的和令人兴奋的功能开辟了道路。正因为这些的改进,有机会让 :has()
看到曙光。到目前为止,你可以在 Safari 15.4+ 和 Chrome Canary(写这篇文章时是 103.0.5011.0 版本)可以看到 :has()
效果。同时也希望Firefox和Chrome也能快速跟上。
说个题外话,最早实现 :has()
选择器的浏览是 Safari,正如 Jen Simmons(@jensimmons)在 Twitter 所言:“不要老说Safari总是最后一个。有时我们是第一”。
自从Jen Simmons加入Safari 的Web开发者体验团队之后,Webkit内核在CSS特性上的更新速度较
如需转载,烦请注明出处:https://www.w3cplus.com/css/css-parent-selector.html
如果文章中有不对之处,烦请各位大神拍正。如果你觉得这篇文章对你有所帮助,打个赏,让我有更大的动力去创作。(^_^)。看完了?还不过瘾?点击向作者提问!