写点什么

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:503627

评论

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

Vue进阶(幺玖捌):js 判断 document.getElementByid(““) 获得的对象是否存在

No Silver Bullet

Vue 11月日更

数据仓库之数据质量建设(深度好文)

五分钟学大数据

11月日更

dart系列之:dart类中的构造函数

程序那些事

flutter dart 程序那些事 11月日更

2021年最新获取url参数的方法,用正则就落后啦

CRMEB

模块三 架构文档

Asha

浏览器存储之 localStorage 和 cookie

devpoint

Cookie LocalStorage sessionStorage 11月日更

Go语言,反射机制,反射三定律!

微客鸟窝

Go 语言 11月日更

【Promise 源码学习】第五篇 - 实现 Promise 对异步操作的支持

Brave

源码 Promise 11月日更

Python Qt GUI设计:窗口布局管理方法【强化】(基础篇—6)

不脱发的程序猿

Python qt GUI设计 窗口布局管理方法

Flutter怎么实现地图导航功能?

坚果

flutter 11月日更

🏆【Alibaba中间件技术系列】「RocketMQ技术专题」帮你梳理RocketMQ相关的消费问题以及原理分析总结

洛神灬殇

RocketMQ 消息队列 11月日更 重复消费

Go语言,面试官问我go逃逸场景有哪些,我???

微客鸟窝

Go 语言 11月日更

孔夫子旧书网数据采集,举一反三学爬虫,Python爬虫120例第21例

梦想橡皮擦

11月日更

Redis计算UV的4种方法

大数据技术指南

11月日更

shell原样输出字符串

WindFlying

C++ 动态新闻推送 第33期

很水

【高并发】不得不说的线程池与ThreadPoolExecutor类浅析

冰河

Java 并发编程 多线程 高并发 异步编程

架构实战营模块5课后作业

胡颖

架构实战营

意外发现,原来你不知道自己每天都在用门面模式

Tom弹架构

Java 架构 设计模式

数据库自增ID用完了会怎样

喵叔

11月日更

Android C++系列:JNI引用管理

轻口味

c++ android 11月日更

sonar 使用常见问题总结

liuzhen007

11月日更

JDK还是Google,正则表达式引擎孰优孰劣?

后台技术汇

正则表达式 11月日更

【Flutter 专题】10 图解底部状态栏小尝试

阿策小和尚

Flutter 小菜 0 基础学习 Flutter Android 小菜鸟 11月日更

一个程序员日常工作中对于Github的一些另类用法

汪子熙

JavaScript GitHub SAP 11月日更

博文推荐|传智教育 x Pulsar:互联网教育的未来

Apache Pulsar

架构 云原生 消息中间件 Apache Pulsar 消息系统 互联网教育

听说你还在写双层for循环解两数之和?

老表

Python LeetCode 11月日更 两数之和 算法与数据结构

C++ 动态新闻推送 第37期

很水

c++

无AI的五子棋Vue项目

空城机

JavaScript typescript 大前端 11月日更

🏆【Alibaba中间件技术系列】「RocketMQ技术专题」帮你梳理RocketMQ或Kafka的选择理由以及二者PK

洛神灬殇

kafka RocketMQ 消息队列 11月日更

模块三-外包学生管理系统架构

小何

「架构实战营」

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