CSS :has 选择器(以及 4 个以上示例)

Avatar of Robin Rendle
Robin Rendle

DigitalOcean 为您旅程的每个阶段提供云产品。从 价值 200 美元的免费积分 开始!

CSS 的 :has 选择器可帮助您选择包含与您传递给 :has() 函数的选择器匹配的元素的元素。它本质上是一个“父级”选择器,尽管它远比这更有用。例如,假设您想选择所有 <div>,但仅在它们包含 <p> 时。这就是我们能做到的

div:has(p) {
  background: red;
}
/*
  <div> <!-- selected! -->
     <p></p>
  <div>

  <div></div> <!-- not selected -->
  <div> <!-- not selected -->
    <section></section>
  </div>
*/

尽管在我写这篇文章的时候它在任何浏览器中都不受支持,但它现在已在 Safari 技术预览版 137 中推出,因此它正在开始发生!

非常方便,对吧!以下是一个示例。假设您希望在标题后添加空格。当然!在您的 h2 上添加一些 margin-block-end 应该就可以了。但是……如果有一个副标题呢?现在我们可以根据副标题是否存在来选择父级并调整间距。

h2,
.subtitle {
  margin: 0 0 1.5rem 0;
}
.header-group:has(h2):has(.subtitle) h2 {
  margin: 0 0 0.2rem 0; /* reduce spacing on header, because subtitle will handle it */
}

/*
  <div class="header-group">
    <h2>Blog Post Title</h2> <!-- main spacing applied here -->
  </div>

  <div class="header-group">
    <h2>Blog Post Title</h2>
    <div class="subtitle"> <!-- main spacing applied here -->
      This is a subtitle
    </div>
  </div>
*/
The CSS :has selector being helpful in spacing headers with subtitles (or not).
在左侧,主要间距出现在 h2 后,在右侧,主要间距出现在副标题后。

我对 :has 的看法是:它是一个父级选择器伪类。这就是 CSS 术语中的“它允许您更改父级元素,前提是它有一个子级或另一个跟随它的元素”。这可能感觉很奇怪!它可能会破坏您对 CSS 工作原理的思维模型。这就是我习惯思考 CSS 的方式

.parent .child {
  color: red;
}

您只能从上到下进行样式设置,从父级到子级,但永远不能向上回到树的顶部。:has 完全改变了这一点,因为到目前为止,CSS 中还没有父级选择器,而且有一些很好的理由。由于浏览器解析 HTML 和 CSS 的方式,如果满足某些条件,选择父级可能会导致各种性能问题。

如果我坐下来仔细考虑今天可能使用 :has 的所有方法,我会有点头疼。它将打开一个以前不可能仅仅用 CSS 实现的潘多拉魔盒。

另一个示例:假设我们只希望将样式应用于包含图像的链接

a:has(> img) {
  border: 20px solid white;
}

这偶尔会很有用。我还可以看到 :has 用于根据元素的内容有条件地向元素添加边距和填充。那将会很酷。

:has 选择器是CSS 选择器级别 4 规范的一部分,与包含极其有用的 :not 伪类的规范相同。


您可以争辩说,CSS :has 选择器比仅仅是“父级”选择器更强大,这正是 Bramus 所做的事情!就像上面的副标题示例一样,您并不一定最终选择父级,您可能会在 has 条件中选择父级,但最终会从中选择子级元素。

/*  Matches <figure> elements that have a <figcaption> as a child element */
figure:has(figcaption) { … }

/* Matches <img> elements that is a child of a <figure> that contains a <figcaption> child element */
figure:has(figcaption) img { … }

在那里,您可以快速看到第二个选择器正在选择一个子级 <img>,而不仅仅是 <figcaption> 的父级。

选择器列表

您可以将其链接

article:has(h2):has(ul) {

}

或为其提供一个选择器列表

article:has(h2, ul) {

}

并且该列表很宽容:W3C 于 2020 年 12 月通过一项决议以响应报告的问题 后,该列表不再“宽容”。因此,如果选择器列表包含一个无效参数,则整个列表将被忽略

/* 👎 */
article:has(h2, ul, ::-blahdeath) {
  /* ::blahdeath is invalid, making the entire selector invalid. */
}

一种解决方法是在其中嵌套一个更宽容的选择器,例如 :is():where()

/* 👍 */
article:has(:where(h2, ul, ::-blahdeath)) {
  /* :where is a forgiving selector, making this valid. */
}

测试支持

@supports(selector(:has(p))) {
  /* Supported! */
}

:not() 选择器是同一规范的一部分……

:has 不同,:not 确实 具有相当不错的浏览器支持,我前几天第一次使用它

ul li:not(:first-of-type) {
  color: red;
}

太棒了,我还喜欢它多么易读;您不必见过这行代码就能理解它在做什么。

您可以使用 :not 的另一种方法是用于边距

ul li:not(:last-of-type) {
  margin-bottom: 20px;
}

因此,不是最后一个项目的每个元素都获得边距。如果您在卡片中有一堆元素,这将很有用,比如这样

……以及 :is():where()

CSS 选择器级别 4 也是包含 :is 选择器的规范,该选择器今天可以在许多浏览器中使用,如下所示

:is(section, article, aside, nav) :is(h1, h2, h3, h4, h5, h6) {
  color: #BADA55;
}

/* ... which would be the equivalent of: */
section h1, section h2, section h3, section h4, section h5, section h6, 
article h1, article h2, article h3, article h4, article h5, article h6, 
aside h1, aside h2, aside h3, aside h4, aside h5, aside h6, 
nav h1, nav h2, nav h3, nav h4, nav h5, nav h6 {
  color: #BADA55;
}

更多信息

就是这样!:has 应该很快就能派上用场,它的兄弟 :is:not 已经非常有用,而这仅仅是这个新规范中可用内容的一小部分——仅三个 CSS 伪类。