- 原文地址:Learn CSS :has() selector by examples: 5 top use cases
- 原文作者:Mojtaba Seyedi
CSS中的 :has
选择器打开了一个充满新可能性的世界。现在它已经登陆 Firefox 121,自此所有现代浏览器都支持它。
在本篇博客中,我们将通过一些示例深入了解 :has
的实用性。和我一起探索这个 CSS 选择器的强大功能吧!
1. 父级选择器
:has()
选择器的主要用途是作为父选择器。它有用于检查父元素是否存在某个元素。
例如,考虑一个场景,你想验证按钮中是否存在图标:
css
button:has(.icon) {
display: flex;
gap: 10px;
}
或者,如果你想在包含子菜单的导航栏项上附加下拉箭头,也可以这样做:
css
nav li:has(ul) > a::after {
content: "+";
margin-inline: 10px;
}
我们搜索列表(ul
)中的列表项(li
),一旦找到,我们就选择链接(a
)并加上内容为加号的伪元素,这里有个在线例子:
:has()
选择器的运行方式与选择器链中的任何其他选择器一样。 您可以在其之前、之后或内部包含任何内容。例如,当 .card
元素包含的 <img>
, 同时<img>
后紧跟着 <p>
时,定位该元素:
css
.card:has(img + p) {
flex-direction: row;
}
这样,我们就可以根据子元素的情况为卡片创建自适应布局:

您还可以在 :has() 内提供用逗号分隔的元素列表,检查它们是否在父元素中:
css
article:has(video, iframe) {
/* 匹配至少包含一个 video 或 iframe 的 <article> */
}
这可确保 article
至少具有其中一个子元素 - <iframe>
或 <video>
。
此外,您可以将 :has()
与 :not()
结合使用以实现更复杂的场景:
css
.card:not(:has(img)) {
/* 匹配不包含 img 的 .card 元素 */
}
.post:has(img:not([alt])) {
/* 匹配包含没有 alt 属性的 img 为子元素 的 .post 元素 */
}
div:not(:has(:not(img))) {
/* 匹配只有 img 子元素的 div 元素 */
}
正如你所看到的,人们可以使用此选择器让 CSS 变得非常有创意,但请记住,不能嵌套 :has()
选择器,但是,你可以链式使用它们:
css
.headings:has(.subtitle:has(h2)) {
/* 无效 */
}
.headings:has(h2):has(.subtitle) h2 {
/* 有效 */
}
最后,还可以检查子元素的状态,以进行表单验证等操作:
css
form:has(input:invalid) {
border: 1px solid red;
}
您可以查看下面例子:
2. 向前兄弟选择器
:has()
选择器不仅限于针对父元素; 它还可以选择前一个的兄弟元素。
例如,你可以根据标签的下一个相邻兄弟元素(如复选框)的状态来设置标签的样式:
html
<label for="checkbox">We need to target this when input is checked</label>
<input id="checkbox" type="checkbox">
你的 CSS 中:
css
label:has(+ input:checked) {
color: green;
}
该 CSS 选择器的目标是 <label>
元素,<label>
其后紧跟着选中的 <input>
元素。
我还在 CodePen 上为这个示例添加了一个示例:
我们并不局限于只选择前一个兄弟节点,我们还可以选择所有前面的兄弟节点。考虑一下有面包屑分隔符的情况。在这种情况下,我们不希望在列表中的最后一个项目后出现任何分隔符,因此我们可以使用 :has()
和同辈分隔符 (~) 来实现这一功能:
css
.breadcrumb-item:has(~ .current)::after {
content: "/";
}
顺便说一句,这个例子是我从 Eric Meyer 那里学来的。
它针对的是 .breadcrumb-item
元素,这些元素的后面同级元素中有 .current
元素,而不一定是直接相邻的元素。
但是,如果我们不使用 .current
类,而只想根据 HTML 标签查找元素呢?你也可以使用 :has()
来实现这一目的:
css
li:has(~ li:not(a))::after {
content: "/";
margin-inline-start: 10px;
}
我们要选择的列表项满足:后面同级列表项中有一个没有 <a>
标签的列表项。
对于本小节中的最后一个示例,让我们来分析一下如何使用我们 has
实现以下效果。
我第一次看到这种技术是在 Chris Coyier 的 CodePen 上。
这个操作的目的是在被悬停的元素之后和之前各选择两个元素,并为它们添加变换效果。
css
li:has(+ li + li:hover) {
transform: scaleY(1.1);
}
li:has(+ li:hover) {
transform: scaleY(1.2);
}
li:hover {
transform: scaleY(1.3);
opacity: 1;
}
li:hover + li {
transform: scaleY(1.2);
}
li:hover + li + li {
transform: scaleY(1.1);
}
如果你想看看这个用法的另一个很酷的实用示例,请查看 Stephanie 在 CodePen 上的星级评定组件。
3. 数量查询
:has()
选择器引入了在 CSS 中执行数量查询的功能,让你可以根据父元素的子元素数量为其设计样式。
以下是我从 Bramus 的精彩博文中摘录的一些例子。
css
/* 至多 3 个子元素,包括 0 个 */
ul:has(> :nth-child(-n+3):last-child) {
outline: 1px solid red;
}
/* 至多 3 个子元素,包括 0 个 */
ul:not(:has(> :nth-child(3))) {
outline: 1px solid red;
}
/* 5 个子元素 */
ul:has(> :nth-child(5):last-child) {
outline: 1px solid blue;
}
/* 至少 10 个元素 */
ul:has(> :nth-child(10)) {
outline: 1px solid green;
}
/* 7 个到 9 个子元素 */
ul:has(> :nth-child(7)):has(> :nth-child(-n+9):last-child) {
outline: 1px solid yellow;
}
我在 Twitter 上看到过一个实用的例子。假设你为一个表格设置了最大高度,但希望在表格有更多数据(特别是达到 5 行时)时取消该限制:
css
.table-wrapper:has(tr:nth-child(5)) {
max-height: none;
}
如果你想体验一下,这里有示例。

该示例展示了如何根据元素内部内容的多少动态更改样式。
4. 任意位置选择器
当与 html
、body
或组件根等顶层元素一起使用时,:has()
选择器允许你根据这些元素中的特定条件应用样式,从而为你提供了广泛的可能性。
例如,您可以根据文档中的 <select>
元素切换主题:
css
body:has(option[value="dark"]:checked) {
--primary-color: #e43;
--surface-color: #1b1b1b;
--text-color: #eee;
}
简单来说,我们要检查我们的 body
元素是否选中了值为 "dark" 的 option
元素。然后,我们更新颜色变量。这也适用于浅色和高对比度主题模式。你可以在下面的演示中看到实际操作:

你也可以使用相同的技术来更改布局。例如,假设你要在 body
中查找一个带有选中 "list" 值的单选 input
,并将特定样式应用到 DOM 中的某个卡片列表:
css
body:has(input[value="list"]:checked) .card-list {
grid-template-columns: 1fr;
}

此外,我最近还在 Rob Bowen 的文章中看到了另一个实用的例子。它涉及在打开弹窗时锁定滚动条。通过检查 body
是否包含作为子元素的弹窗,可以更改 body
的 overflow
属性。
css
body:has(.modal) {
overflow: hidden;
}
5. 除我之外的所有选择器
由 :has()
实现的 all-but-me 选择器可以选择除被交互元素以外的所有元素。例如,当鼠标悬停在一个子元素上时,您可以选择除被悬停元素以外的所有同级元素。查看此示例来看是如何实现的。

这是选择器:
css
.card-list:has(.card:hover) .card:not(:hover) {
filter: blur(4px)
}
该选择器的内容如下:当容器的子元素卡片悬停时,选择所有未悬停的卡片。
最后一个示例到此结束。我已将所有这些示例添加到我的 CodePen 账户上的一个集合中。我计划今后加入更多示例,并不断更新该集合。
总结
重要的是要注意不要过度依赖 :has
这样的选择器。虽然选择器功能强大,但过度使用可能会让你忽略更简单的选择器写法。取得平衡对于编写易于维护的代码至关重要!
如果你想进一步了解 :has
选择器,我的朋友 Geoff 和我在 CSS-Tricks 上写了一篇博文。欢迎阅读,了解更多信息!