写点什么

CSS 选择器的一场革命,:has() 高级使用指南

作者 | Rahul Chhodde

  • 2023-07-10
    北京
  • 本文字数:4054 字

    阅读完需:约 13 分钟

CSS 选择器的一场革命,:has()高级使用指南

多年以来,CSS:has()伪类一直是最受期待的功能之一。这是一个 L4 级 CSS 选择器,目前已经在 Crhome 105 中受到完全支持,而且有望在短时间内快速普及到更多浏览器当中。

 

CSS 中的 :has() 是一个关系伪类,用于检查给定元素中是否包含某些子元素,如果符合匹配条件则将其选定,之后对样式做相应设置。

 

本文解释了 :has() 选择器的适用场景、一般用法、从简单到高级的各种用例、浏览器兼容性以及后备替换方案。

 

 :has() 选择器的现实意义

 

大多数开发人员习惯用 JavaScript 来实现那些 CSS 默认不支持的功能。然而,如今的网络浏览器已经发展得极其强大,也为各种新奇有趣的 CSS 功能打开了大门。

 

 :has() 选择器就是这样的功能之一。它适用于父元素而非子元素,使用逗号分隔的选择器列表作为参数,负责在它所表示的元素的各子元素间查找匹配项。其功能与 jQuery  :has() 方法颇为相似。

 

下面我们将一同回顾前端开发中 :has() 选择器的一些适用场景,了解开发者群体为何多年来一直期盼这项功能的实践落地。

 

首先自然是检查某个元素中是否包含其他某些元素,而后对其样式做相应设置。以往我们只能通过 JavaScript 实现这项操作,具体如下所示:

 

let content = document.querySelector("#content"),    headings = [] if(content) {  headings = content.querySelectorAll("h1, h2, h3")}if(headings.length) {  // do something}
复制代码

 

在以上代码中,我们可以使用 Web API 方法 querySelectorAll 来检查分区元素的标题。此方法将返回所提交元素的 NodeList。

 

如果 NodeList 为空,则意味着直接父级中不存在任何元素。JavaScript 版本的实现方式请参阅此处:

https://codepen.io/_rahul/pen/eYVQGwE

 

CSS:has()版本的实现方式请参阅此处:

https://blog.logrocket.com/advanced-guide-css-has-selector/#checking-multiple-children

 

其次是从子元素中选择父元素的能力。同样的,由于之前 CSS 不提供可直接操作的工具,所以开发人员习惯于用 Web API 的 parentNode 属性来实现:

 

let el = document.querySelector(".someElement"),    elParent = nullif(el) {  elParent = el.parentNode}
复制代码

 

上述代码的 CodePen 演示请参阅此处:

https://codepen.io/_rahul/pen/vYdQPMN

 

:has()选择器的实现请参阅此处

https://blog.logrocket.com/advanced-guide-css-has-selector/#selecting-parent

 

最后,:has() 选择器还能选择给定元素的上一个同级元素。CSS 中的同级选择器只允许选择下一个同级元素,但无法单凭 CSS 选择上一个同级元素。JavaScript 的实现如下所示(CodePen 完整演示:

https://codepen.io/_rahul/pen/oNEQKyz

 

let el = document.querySelector(".someElement"),    elPs = nullif(el) {  elPs = el.previousElementSibling}
复制代码

 

:has() 版本的完整代码请参阅此处:

 

https://blog.logrocket.com/advanced-guide-css-has-selector/#selecting-previous-sibling

 

可以看到,我们前面用 JavaScript 形式实现的所有功能,如今都能通过 CSS:has()搞定。下面,我们一同了解如何在谷歌 Chrome 上启用并测试这项功能。

 

启用 :has() 选择器

 

如果大家用的仍然是较旧的 Chrome 版本(v.101 至 104),则可通过 Chrome 标记启用此项功能。请确保您使用的是 Chrome 101 及以上版本,而后通过浏览器地址栏直接导航至 chrome://flags。

 

接下来将 Experimental Web Platform features 设置为 Enabled 即可。请重新启动浏览器,之后您就可以在 Chrome 浏览器中使用 CSS:has()了。



CSS :has() 选择器有啥作用?

 

现在咱们聊聊:has()伪类的用法和属性。作为一个伪类,它可以被附加至任何带有冒号的选择器上,并将接收到的类、ID 和 HTML 标签当作参数使用。

 

以下代码为:has()的常规用法和语法。仅当.selector 类包含使用:has()伪类以参数形式传递来的元素时,该类才会被选中:

 

.selector:has(.class) { ... }.selector:has(#id) { ... }.selector:has(div) { ... }
复制代码

 

可链接性

只要认为合适,大家完全可以一个接一个把多个:has()伪类链接起来。以下代码演示了此类链接的实现方式:

 

.selector:has(div):has(.class):has(#id) {  ...}
复制代码

 

通过参数列表实现多项选中

也可以提供一个包含多个元素的选择器列表,其形式与链接类似但效率更高:

 

.selector:has(div, .class, #id) {  ...}
复制代码

 

灵活性

 

假设大家意外向:has()伪类提交了无效的元素选择器,也请不必担心。:has()相当聪明,能够忽略掉无效项目并只处理有效选择器:

 

.selector:has(div, accordion, .class, ::lobster, #id) {  ...}
复制代码

 

accordion 和 ::lobster 属于无效选择器,这里会被:has()直接忽略。大家在开发者工具中不会看到任何 CSS 错误或警报。

 

使用场景

 

下面,我们来看 CSS:has()选择器在各种具体场景下的应用示例。

 

选中父元素

 

这可能是:has()选择器最常见的用法,因为其默认行为是选中包含一组特定元素的内容。但如果我们只知道其中的子元素,能否选中相应的父元素?





 通用选择器(*)可以跟:has()以及子组合器(>)配合使用,在无需了解相关信息的前提下快速选中父元素。

 

检查是否存在多个子元素

 

如前文属性讨论部分所述,:has()允许我们传递包含多个实体的列表,因此能够在给定元素中检查任意数量的选择条件。



选中上一个同级元素

 

通过将 CSS 相邻同级组合器同:has()伪类相结合,即可选中上一个同级元素。

 

有些朋友可能已经知道,相邻同级组合器能够选中给定元素的下一个同级元素。把这项操作跟:has()相结合,就能获取上一个同级元素。简单来讲,只要某个元素拥有下一个同级元素,那就可以用:has()加上+ 组合器的方式把这个元素选中!



有条件修饰

 

使用:has() 选择器,我们就不必单独对带有或不带有某些子元素的元素做单独设置。最典型的示例,自然就是带标题和不带标题的各种图形元素。



 假定有这样一个 figcaption 元素,其中不包含任何文本,我们该如何处理?对于这种情况,只要使用:not 和 :empty 选择器即可快速检查其内容。

 

与图形修饰类似,我们还可以在多个段落间切换各个引用块的文本对齐方式,具体请参阅此处的实际效果:

https://codepen.io/_rahul/pen/MWQZXoY

 

为空状态设置样式

 

之前我们已经拥有名为:empty 的伪类,用于对不包含任何内容的元素作样式设置。但它对空状态的处理存在问题,即使只包含一个空格,:empty 也会将该元素识别为非空。

 

这时候使用:empty 肯定效果不佳。假定我们要创建一个卡网格,其中包含一些无内容卡,这时就可以使用:has() 选择器来设计空卡状态的样式。



类型和块调整

 

在设计文章样式时,将类型和块元素保持对齐始终是项棘手的工作。下面,我们考虑同时涉及代码块、图形、块引用等通用类型元素的场景。

 

块元素间应该留有更多的垂直间距和修饰,以便在不同的字体条件下始终保持鲜明。这里可以用 CSS 功能来实现。

 

p {  margin: 0;}p:not(:last-child) {  margin-bottom: 1.5em;}h1,h2,h3,h4,h5,h6 {  line-height: 1.3;  margin: 1.5em 0 1.5rem;  color: #111;}pre,figure,blockquote {  margin: 3em 0;}figcaption {  margin-top: 1em;  font-size: 0.75em;  color: #888;  text-align: center;}
复制代码

 

以上代码片段会在标题和块元素之间生成不均匀的垂直间距,如下所示。要解决这个不均匀问题,我们可以使用之前提到的同级选中技巧试试看。

 


CTA 图标化


假定大家在 CSS 中创建了具有两种变体的 CTS(召唤按钮)或按钮组件:其一为默认的普通按钮,其二是带有图标的按钮。

 

以往我们恐怕需要为此编写两个单独的类,但现在有了:has()选择器,再也不必如此麻烦。我们只需要检查.btn 元素中的.btn-icon 子元素,然后将其设置成相应的样式即可。



布局调整


假设标题组件中有两种布局变体:其一是固定宽度,其二是动态宽度。

 

为了将标题内容保持在固定的宽度之内,我们必须在其中添加一个内容打包元素。这两个标头版本的标记间的唯一区别,就体现在打包元素上。

 

之后可以将 :has() 和 :not() 选择器配对使用并添加 CSS 类。



调整布局还有另一种用法,就是在网格中的列达到一定数量之后立即对其执行修改。

 

如果大家不想用 minmax() 函数来确定网格中各列的最适合宽度,那这种方法也很方便。

可以看到,只要标记超过两项,网格就会自动调整为三列。

 

改善表单可用性


只有将交互设计与反馈之间正确匹配起来,才能为交互设计找到最好的规划方向。而在设计交互式 HTML 表单时,向用户提供关于其输入的反馈显然是提升使用体验的大妙招。

 

在:has()、 :valid 和 :invalid 的帮助下,我们可以让表单更加动态,而且完全无需劳烦 JavaScript。



检查浏览器是否支持


如果浏览器不支持:has() 选择器,应该让以上示例触发错误弹窗以发出提醒。这种效果可以使用 @supports  CSS 规则实现,如以下代码所示:

 

@supports (selector(:has(*))) {  .selector:has(...) {    ...    }}
复制代码

 

大家可以这样一步步检查浏览器支持,并根据需要设置元素样式。如果某些代码库中直接用到某些新功能,则可通过以下方法为相同功能编写向下兼容的版本:

 

@supports not (selector(:has(*))) {  .selector {    ...    }}
复制代码

 

采取同样的方法,大家可以在浏览器不支持时向用户发出提醒。

 

还可以使用 JavaScript 中的 Support API 检测浏览器对不同功能的支持。以下为纯 JavaScript 版本的:has()支持检查示例:

 

if(!CSS.supports('selector(html:has(body))'))) { // if not supported  // Use the conventional JavaScript ways to emulate :has()}
复制代码

 

总结


在本文中,我们共同了解了 L4 级 CSS 选择器:has()。我们探讨了其现实意义,能够在前端项目中替代哪些 JavaScript 功能,以及从普通到高级的各种应用案例。

 

我们还涉及到不同浏览器的当前支持情况,以及如何检查您的浏览器是否支持这项新功能。

 

感谢耐心阅读,希望本文为您带来一点启发和帮助。欢迎大家在评论中分享更多关于:has()的实际应用案例。


相关阅读:


CSS 样式中颜色与颜色值的应用

避免使用 CSS @import 影响页面加载速度

css 过去及未来展望—分析 css 演进及排版布局的考量

这 10 个强大的 CSS 属性,每个前端都要懂

2023-07-10 17:503991

评论

发布
暂无评论
发现更多内容

区块链作用之数字货币的影响

v16629866266

IDEA 异常退出 解决方法

任广印

IDEA

Elasticsearch 批量查询 mget

escray

elastic 七日更 28天写作 死磕Elasticsearch 60天通过Elastic认证考试

开发质量提升系列:用户体验

罗小龙

最佳实践 方法论 28天写作

AI、IoT、区块链、自主系统、下一代计算五大技术引领未来供应链发展

京东科技开发者

区块链 AI IoT 供应链

美国大选期间美股迎来大涨,舆情到底有何魔力?

星环科技

人工智能 大数据

高阶段位机房管理:3D集装箱数据中心,触发科技“火苗”的燃烧

一只数据鲸鱼

数据可视化 3D可视化 机房管理 数据中心可视化 集装箱式数据中心

面对key数量多和区间查询低效问题:Hash索引趴窝,LSM树申请出场

华为云开发者联盟

数据库 数据 存储 Hash索引 LSM树

漫谈HTTP协议

架构精进之路

HTTP 七日更 28天写作

《论雨伞道德》- 不要和自己的良心捉迷藏

石云升

读书笔记 28天写作 雨伞道德

[高并发]高并发分布式锁架构大解密,不是所有的锁都是分布式锁!!

Geek_0o5u34

你会读书吗?

xcbeyond

读书感悟 读书方式 28天写作

【CSS】波纹效果

德育处主任

CSS小技巧 28天写作 纯CSS

HTML5中的拖放功能

我是哪吒

html html5 程序员 面试 大前端

代码 or 指令,浅析ARM架构下的函数的调用过程

华为云开发者联盟

函数 任务栈 arm架构

CSS实现数据统计

德育处主任

大前端 CSS小技巧 28天写作 纯CSS

【CSS】不规则阴影

德育处主任

css3 html/css CSS小技巧 28天写作 纯CSS

机器学习应用设计阶段的 10 个陷阱和 11 个最佳实践

机器学习

前端模拟假数据(json-server光速入门篇)

德育处主任

json 大前端 Node 28天写作 json-server

音视频行业不可或缺的功能-云端录制

anyRTC开发者

音视频 WebRTC 在线教育 直播 RTC

甲方日常 91

句子

工作 随笔杂谈 日常

Vue 3自定义指令开发

葡萄城技术团队

Volcano 监控设计解读,一看就懂

华为云开发者联盟

Kubernetes 云原生 监控 Volcano 计算

微服务容错时,这些技术你要立刻想到

华为云开发者联盟

微服务 线程 服务雪崩 断路器 服务降级

即构SDK新增焦点语音功能,可实现特定用户语音的聚焦

ZEGO即构

灵雀云Kube-OVN进入CNCF沙箱,成为CNCF首个容器网络项目

York

灵雀云 Kubernetes Kube-OVN

个人隐私之老话重谈

张老蔫

28天写作

Vue3 中 v-if 和 v-show 指令实现的原理 | 源码解读

五柳

源码分析 大前端 Vue3

数据中台:建立在数据网络效应之上的赛道

奇点云

大数据 数据中台 云原生 数据

前端知识总结输出文章目录大全

梁龙先森

JavaScript 大前端 编程语言 28天写作

【Android Tips】小厂的扫码还能怎么做?

李小四

机器学习 二维码 扫码 微信扫码

CSS 选择器的一场革命,:has()高级使用指南_编程语言_InfoQ精选文章