[译] 通过示例学习 CSS :has() 选择器: 5 个顶级使用案例

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. 任意位置选择器

当与 htmlbody 或组件根等顶层元素一起使用时,: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 是否包含作为子元素的弹窗,可以更改 bodyoverflow 属性。

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 上写了一篇博文。欢迎阅读,了解更多信息!

相关推荐
一只小风华~9 分钟前
Web前端:JavaScript和CSS实现的基础登录验证功能
前端
90后的晨仔9 分钟前
Vue Router 入门指南:从零开始实现前端路由管理
前端·vue.js
LotteChar18 分钟前
WebStorm vs VSCode:前端圈的「豆腐脑甜咸之争」
前端·vscode·webstorm
90后的晨仔20 分钟前
零基础快速搭建 Vue 3 开发环境(附官方推荐方法)
前端·vue.js
洛_尘32 分钟前
Java EE进阶2:前端 HTML+CSS+JavaScript
java·前端·java-ee
孤独的根号_35 分钟前
Vite背后的技术原理🚀:为什么选择Vite作为你的前端构建工具💥
前端·vue.js·vite
吹牛不交税1 小时前
Axure RP Extension for Chrome插件安装使用
前端·chrome·axure
薛定谔的算法1 小时前
# 前端路由进化史:从白屏到丝滑体验的技术突围
前端·react.js·前端框架
拾光拾趣录2 小时前
Element Plus表格表头动态刷新难题:零闪动更新方案
前端·vue.js·element
Adolf_19933 小时前
React 中 props 的最常用用法精选+useContext
前端·javascript·react.js