:has()可声明式实现子元素数量驱动的栅格切换,避免JS手动更新class导致的漏更新、SSR闪屏等问题,但需避开DOM结构陷阱、用正向:nth-child()定位、包裹@supports检测并处理空白节点。

能显著提升布局效率,但前提是避开 DOM 结构陷阱、用对数量判断逻辑、并包裹 @supports 条件。 它不是万能补丁,而是把“结构即状态”的逻辑交还给 CSS,省去 JS 统计、class 切换和 SSR 闪屏问题。
如何用 :has() 实现子元素数量驱动的栅格切换
响应式布局中常需“3 项以上用 grid,否则 flex 横排”,传统做法靠 JS 数 childElementCount 再加类,容易漏更新。用 :has() 可声明式实现:
-
.list:has(> .item:nth-child(4))→ 至少 4 项,触发三列 grid(grid-template-columns: repeat(3, 1fr)) -
.list:has(> .item:nth-child(3)):not(:has(> .item:nth-child(4)))→ 恰好 3 项,用两列 - 避免写
:nth-last-child(2)这类反向锚点,易受空白文本节点干扰;优先用:nth-child(n)正向定位 - 若子项可能含注释或换行空格,先用
display: contents或预处理 HTML 去除冗余文本节点
为什么 :has() 比 JS 动态加 class 更可靠
JS 方案在以下场景会出问题:
-
appendChild()后忘记调用updateLayoutClass(),样式卡在旧状态 - SSR 渲染的 HTML 初始无 class,首屏 layout shift 明显
- React 中
useEffect监听ref.current.children.length,但children不包含slot分发内容 -
:has()是浏览器原生响应机制,DOM 插入/删除后自动重匹配,无需手动触发
:has() 在生产环境必须加 @supports 包裹
Chrome 105+、Firefox 121+、Safari 15.4+ 已稳定支持,但 IE 完全不支持,旧版 Safari 对嵌套 :has() 有 bug。必须这样写:
立即学习“前端免费学习笔记(深入)”;
@supports (selector(:has(*))) {
.card:has(.tag.hot) {
border-color: #f59e0b;
}
}
注意:@supports selector() 是唯一可靠检测方式,@supports :has 语法无效;若项目需兼容 Safari ≤15.3,应降级为 JS fallback 或预设 class。
最常被忽略的是:哪怕只多一个换行符,:has(> .item) 就可能匹配失败——因为那个换行是 text node,.item 实际是 :nth-child(2)。别假设 HTML 是“干净”的。


















