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

新闻资讯   2023-07-12 15:03   116   0  


作者 | Rahul Chhodde
译者 | 核子可乐
策划 | 丁晓昀

多年以来,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 属性来实现:

上述代码的 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

: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() 的实际应用案例。

今日好文推荐

TypeScript中的Object.keys类型为什么非要这么设计?

前端容易出神话?从高中辍学到价值 10 亿美元的初创公司

写给开发者的Vision Pro评测:易上手,但UI设计不够“革命”

AI浏览器扩展:一场新的安全噩梦

文章引用微信公众号"前端之巅",如有侵权,请联系管理员删除!

博客评论
还没有人评论,赶紧抢个沙发~
发表评论
说明:请文明发言,共建和谐网络,您的个人信息不会被公开显示。