首页 > 解决方案 > CSS 根据具有特定子项的邻居选择元素

问题描述

这是一个网页的简化结构。

<div>
    <div>div 1</div>
    <div>div 2<a href="/foo">foo</a></div>
    <p>select me</p>
    <p>keep me alone</p>
</div>

我想p根据里面的邻居div选择所有a内容。这是我最好的尝试:

document.querySelectorAll('(div > a[href="/foo"]) ~ p')

但显然这不是一个有效的 CSS 选择器。

是否可以选择p是否有div内部邻居a

标签: htmlcsscss-selectors

解决方案


如果 p 有一个带内部的邻居 div,是否可以选择它?

您需要使用:has()选择器,但完整的解决方案取决于您是要在“实时配置文件”还是“快照配置文件”中使用它:

实时配置文件适用于任何上下文,包括实时的浏览器 CSS 选择器匹配。它包括本文档中定义的每个选择器,除了:has()

快照配置文件适用于对性能不是非常敏感的上下文;特别是,它适用于根据静态文档树评估选择器的上下文。例如,querySelector()DOM 中定义的方法应该使用快照配置文件。

具体讲的时候:has()

  • .css/<style>样式表规则选择器(这是一个实时上下文):
  • .css/ <style>stylesheet 规则选择器中允许使用客户端脚本:has()(这意味着它实际上是使用快照上下文):
  • querySelectorquerySelectorAllmatches其他 DOM 函数(这是快照上下文)中:
    • 但截至 2020 年 5 月,没有浏览器原生支持:has()在这些功能中使用。
    • 经过一番粗略的谷歌搜索,我找不到一个 polyfill 或补丁来添加:has()querySelector等的支持,嘘!

但是假设您可以使用:has()(例如,因为您在 2030 年阅读这篇文章),那么您只需要这个查询:

div:has(> a[href="/foo"]) ~ p

为什么不在:has()Live Profile 中?

CSS 不支持根据子元素选择元素的原因有很多(尽管有一些例外,例如:empty:focus-within),但主要原因是:

  • 这将使 CSS 选择器的计算成本更高。
  • CSS 设计用于处理部分加载的文档,这样在加载文档时它不会显着改变外观。

作为一个思想实验:假设浏览器确实支持:has()CSS 样式规则,考虑一个看起来像这样的 HTML 文档:

<html>
<head>
    <style>
    p { background-color: red; }
    p:has(div) { background-color: blue; }
    </style>
</head>
<body>
    <p>foo <div></div></p>
    <p>bar</p>
    <p>baz <div></div></p>
</body>
</html>

“foo”和“baz”元素为蓝色,“bar”元素为红色。

现在,如果浏览器有这个文档处于部分加载状态,这样它到目前为止只加载了这个(在用户的 Wi-Fi 连接断开之前) - 或者在下一个 TCP 数据包到达之前:

<html>
<head>
    <style>
    p { background-color: red; }
    p:has(div) { background-color: blue; }
    </style>
</head>
<body>
    <p>foo

浏览器需要通过为截断元素插入自己的结束标签来干净地终止部分加载的文档,如下所示:

<html>
<head>
    <style>
    p { background-color: red; }
    p:has(div) { background-color: blue; }
    </style>
</head>
<body>
    <p>foo</p>
</body>
</html>

因此,当文档处于此状态时,第一个p将是红色,而不是蓝色 - 然后在用户的 Wi-Fi 连接重新连接(或下一个 TCP 数据包到达)并且页面完成下载然后<p>foo</p>获得<div>等之后:has(div)规则现在适用并且意志突然从<p>foo</p> 红色变为蓝色——这不好。

现在在这个人为的例子中,像背景颜色变化这样的微不足道的差异并不重要 - 但是如果:has()选择器用于控制页面布局中的一些主要差异(例如body:has(footer) > main { display: grid;),那么这真的会搞乱页面布局虽然它仍在加载 - 这就是为什么现在不支持基于后代内容的选择器的当前形式。

(但是鉴于父母选择者的实用性和实用性,我想将来会以某种方式限制使用规则,仅在启动DOMContentLoaded或要求选择者不确定时使用默认或后备,将来会重新引入它们.


推荐阅读