【:has()】 CSS关系性伪类介绍及实战

目录

[第一章 前言](#第一章 前言)

[第二章 :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() ------ 一行代码,体验翻倍。

相关推荐
谎言西西里3 小时前
零基础 Coze + 前端 Vue3 边玩边开发:宠物冰球运动员生成器
前端·coze
努力的小郑3 小时前
2025年度总结:当我在 Cursor 里敲下 Tab 的那一刻,我知道时代变了
前端·后端·ai编程
GIS之路3 小时前
GDAL 实现数据空间查询
前端
OEC小胖胖3 小时前
01|从 Monorepo 到发布产物:React 仓库全景与构建链路
前端·react.js·前端框架
2501_944711434 小时前
构建 React Todo 应用:组件通信与状态管理的最佳实践
前端·javascript·react.js
困惑阿三4 小时前
2025 前端技术全景图:从“夯”到“拉”排行榜
前端·javascript·程序人生·react.js·vue·学习方法
苏瞳儿4 小时前
vue2与vue3的区别
前端·javascript·vue.js
weibkreuz5 小时前
收集表单数据@10
开发语言·前端·javascript
hboot6 小时前
别再被 TS 类型冲突折磨了!一文搞懂类型合并规则
前端·typescript
在西安放羊的牛油果6 小时前
浅谈 import.meta.env 和 process.env 的区别
前端·vue.js·node.js