目录
[第一章 前言](#第一章 前言)
[第二章 :has() 语法速记](#第二章 :has() 语法速记)
[第三章 实用场景](#第三章 实用场景)
[第四章 性能 & 兼容性](#第四章 性能 & 兼容性)
[第五章 总结](#第五章 总结)
第一章 前言
小编最近刚好遇到了两个样式的需求,如下:
需求1:(原因:是element-ui弹窗的层级低于嵌入ifram第三方ui的内置层级)

但是element-ui有以下两情况,有时class="v-modal"蒙层会在class="el-dialog__wrapper login-dialog"上面,有时会在下面,而且小编发现项目中使用到的所有element的弹窗组件都会有class="v-modal",那么就不能直接修改class="v-modal"的z-index层级(会影响到全局的),但是只修改class="el-dialog__wrapper login-dialog"又不会生效;


解决方案:小编添加了login-dialog类名,然后想通过login-dialog类名去获取class="v-modal",如痴操作的就相当于针对性的只修改了一处的样式了;但是问题来了,两个样式如何关联上呢?此时小编想到了:has!
处理如下:
css
// v-modal后面有login-dialog的兄弟节点改v-modal样式
.v-modal:has(~ .login-dialog) {
z-index: 9999 !important;
}
// v-modal是login-dialog的下一个兄弟节点改v-modal样式
.login-dialog + .v-modal {
z-index: 9999 !important;
}
需求2:移动端v-html解析后有这么个样式;由于layui-table太宽导致页面放不下,现在需要把他上一级的div标题添加滚动条,但是div又没有类名:

解决方案:
css
/* 选中"包含 .layui-table 的 div"并给它加滚动 */
div:has(> .layui-table) {
overflow: auto;
/* 想横向滚动就再加 */
max-width: 100%;
}
:has() 让 CSS 第一次拥有了"父级/前兄弟"判断能力。小编用上面两个需求实战开篇,是不是让大家感觉到眼前一亮的感觉!后面小编还会再给出 10 个"哇,原来还能这样"的实用场景,全部复制即可用,一次性把 :has() 写进肌肉记忆。
第二章 :has() 语法速记
- A:has(B) :A 内部任意位置包含 B(后代)
- A:has(> B) : A 的直接子节点是 B
- A:has(+ B) :A 的下一个相邻兄弟是 B
- A:has(~ B) :A 后面所有兄弟里至少有一个 B
- A:has(B, > C) : 多条件:或关系
css
A:has(B) /* A 内部任意位置包含 B(后代) */
A:has(> B) /* A 的直接子节点是 B */
A:has(+ B) /* A 的下一个相邻兄弟是 B */
A:has(~ B) /* A 后面所有兄弟里至少有一个 B */
A:has(B, > C) /* 多条件:或关系 */
注意:
- "前兄弟"选不到,只能"向后"选;需要反向时让共同祖先兜底 / 或者用正常的+ > ~ 都可
- 参数里可以放任意"复合选择器",但不能放伪元素(::before 等)。
第三章 实用场景
|----|--------------------------|-----------------------------------------------------------------------|-----------|
| | 场景 | 代码 | 体验提升点 |
| 1 | 空购物车隐藏结算栏 | .cart:has(.item:empty) .checkout { display:none; } | 无需 JS 计算 |
| 2 | 表单项必填且未填时高亮提交按钮 | form:has(.required:invalid) button[type=submit] { opacity:.4; } | 实时反馈 |
| 3 | 图片没加载完先不显示容器 | .img-box:has(img[loading]) { visibility:hidden; } | 防止抖动 |
| 4 | 标签页当前高亮 | .tabbar:has(#tab2:checked) label[for=tab2] { color:var(--active); | 纯 CSS 标签页 |
| 5 | 评论区有敏感词直接隐藏 | .comment:has(.word-filter:checked) { display:none; } | 运营秒审 |
| 6 | 表格某列全为 0 时整列隐藏 | table:has(td:nth-child(3):not(:zero)) .col-3 { visibility:collapse; } | 数据自适应 |
| 7 | 文章里出现代码块就加顶部"复制"按钮 | .post:has(pre) .copy-btn { display:inline-flex; } | 渐进增强 |
| 8 | 卡片组图任意一张加载失败就换兜底图 | .gallery:has(img:error) { background-image:url(fallback.svg); } | 故障容灾 |
| 9 | 导航栏吸顶时给主体加 padding(避免跳动) | body:has(.nav.stuck) main { padding-top:60px; } | 滚动顺滑 |
| 10 | 深色模式自动判断(检测到深色图片) | body:has(img[src*="dark"]) { color-scheme:dark; } | 彩蛋式主题 |
第四章 性能 & 兼容性
- 尽量用"近距离"关系
:has(> B) / :has(+ B) 搜索深度最浅,回流最小。
- 不要嵌套 :has()
浏览器已做短路优化,但两层以上可读性骤降,可拆中间 class。
- 老浏览器优雅降级模板
css
/* 1. 现代浏览器 */
@supports selector(:has(+ *)) {
.v-modal:has(+ .login-dialog) { ... }
}
/* 2. 旧浏览器走 JS 方案 */
.v-modal.login-active { ... } // 由脚本添加
- 与 Tailwind / UnoCSS 结合
javascript
<div class="md:has-[table]:overflow-x-auto">
第五章 总结
:has() 把 CSS 从"只能向后"升级为"可以向前看",让"结构判断"真正留在了样式层。 今天遮罩改色,明天空态隐藏,后台表格折叠......凡是以前必须写 JS 的"如果里面有××就改外面",现在都可以先试试 :has() ------ 一行代码,体验翻倍。