:has() 可纯 CSS 检测子元素存在性并动态切换父容器布局,如 .card:has(img) 设为 flex,.card:not(:has(img)) 保持 block;支持与媒体查询、自定义属性、表单验证及列表计数组合使用,但需注意浏览器兼容性与性能限制。

用 :has() 检测子元素存在性并切换父容器布局
直接改父元素的 display、flex-direction 或 grid-template-columns,无需 JS 或额外 class。关键在于把“是否含某子元素”变成样式触发条件。
-
.card:has(img)匹配所有含<img>的卡片,可设为display: flex;而.card:not(:has(img))保持display: block - 注意:括号内选择器是相对父元素评估的,
.card:has(.thumbnail)能匹配后代任意深度的.thumbnail,不局限于直系子元素 - 避免写成
.card:has(img):not(:has(video))这类嵌套否定——浏览器解析可能不稳定,改用两个独立规则更可靠
响应式断点 + :has() 组合控制复杂布局
当需要“在宽屏下、且卡片含图片时才启用横向布局”,就得叠加媒体查询与 :has()。二者不是互斥,而是分层条件。
- 先写媒体查询限定视口范围:
@media (min-width: 768px) - 再在其中使用
:has()筛选内容结构:.card:has(img) { display: flex; } - 不要把
:has()放在媒体查询外层(如body:has(.card img) @media {...}),语法非法 - 实际项目中,常配合 CSS 自定义属性做状态透传,比如
.card:has(img) { --has-media: 1; },后续用var(--has-media)控制其他样式
:has() 处理表单验证类条件布局
表单提交前需高亮有错误的区域,但传统方式得靠 JS 加 class。用 :has() 可纯 CSS 实现“任一输入无效 → 整个表单背景变黄”。
-
form:has(input:invalid)是最简写法,注意:invalid是伪类,不是属性,不能加引号 - 若需更精确控制(比如只响应必填字段),可用
form:has(input[required]:invalid) - Firefox 121+、Chrome 105+、Safari 15.4+ 已稳定支持,但 Edge 旧版本可能 fallback 失败,建议搭配
@supports (selector(:has(*)))做渐进增强 - 慎用于深层嵌套表单,
:has()性能开销随 DOM 深度上升,超过 5 层嵌套时建议拆分逻辑
用 :has() 配合 :nth-last-child() 动态调整列表容器
列表项数量影响整体布局时(比如 4 项以上缩窄卡片宽度),:has() 可作为“计数开关”,比 JS 更轻量。
立即学习“前端免费学习笔记(深入)”;
-
.list:has(li:nth-last-child(n+5))表示“至少有 5 个<li>”,此时可改--item-width: 150px - 不要写
.list:has(li:nth-of-type(5))——它只检测第 5 个是否为<li>,而非总数是否 ≥5 - 若列表含非
<li>元素(如注释或空文本节点),:nth-last-child仍会将其计入,容易误判;建议确保列表结构干净 - 该组合在 Safari 中对动态插入/删除列表项的响应有轻微延迟(约 1–2 帧),视觉敏感场景需留意
真正难的不是写对 :has() 语法,而是判断哪些条件值得交给它——比如“是否含图片”适合,但“图片加载完成与否”就不行,后者仍需 img:loaded(尚未标准化)或 JS。


















