:not(:has(*))是真正“完全无子”的判断,匹配不含任何子节点(含文本、注释、元素)的父元素,比:empty更严格,但受空格、换行、注释影响,仅现代浏览器支持。

用 :not(:has(*)) 判断“真正空”的容器
要隐藏没有子节点的列表容器,不能依赖 :empty——它会被换行符、空格、注释等文本节点干扰。真正可靠的写法是 .list:not(:has(*)),它只在容器内**完全无任何子节点**(包括元素、文本、注释)时生效。
-
:empty匹配失败的典型场景:<ul class="list">\n</ul>(含换行符)→ 不匹配 -
:not(:has(*))要求 DOM 中连空白文本节点都不能有,即必须是<ul class="list"></ul>(同一行、无空格) - 模板渲染(如 Vue 的
v-if、React 的条件 JSX)更容易产出这种“真空”结构,适合直接配:not(:has(*))
为什么不用 :has(.item) 或 :empty
.list:has(.item) 是反向思路:它选中“有列表项”的容器,得再叠加 :not() 才能隐藏空的,多一层否定且语义绕。而 :empty 在服务端渲染或手写 HTML 里极易失效——只要编辑器自动加了缩进或换行,它就静默不工作。
-
:has(.item)只检测特定类名,若列表项是<li>但没加.item,规则就漏判 -
:empty对<ul></ul>(带空格)或<ul><!-- comment --></ul>完全无效 -
:not(:has(*))是唯一能覆盖所有子节点类型的判断方式,语义最直白:“没有任何东西”
Safari 和旧版 Firefox 的兼容性陷阱
:not(:has(*)) 在 Safari 15.4+ 和 Firefox 126+ 才稳定支持。更早版本(如 Safari 15.4–15.6)存在样式延迟:DOM 清空后需触发重排才生效;Firefox 125 及更早则直接忽略整个规则。
- 检查兼容性务必访问 caniuse.com/css-has,别只看“支持”标签
- 若需兼容 Safari 15.3 或 Firefox childList 变更 + 切换
.is-empty类 - 注意:Shadow DOM 内部无法被外部
:has()捕获,如果列表在 Web Component 里,这条规则天然失效
别让 display: contents 暗中破坏规则
如果列表容器设置了 display: contents,它会从渲染树中移除自身节点,导致其子节点“升格”为父容器的直接子节点——此时 .list:not(:has(*)) 永远不会匹配,因为 * 实际上已不属于它的后代范围。
- 这个失效是静默的:浏览器既不报错,也不应用样式
- 排查时先临时删掉
display: contents,看规则是否恢复生效 - 同理,
<slot></slot>投影内容、自定义元素内部的结构,都无法被外层:has()触及
真实空容器的判定比表面看起来更脆弱——一个看不见的换行符、一行注释、甚至 display: contents 这样的布局声明,都可能让 :not(:has(*)) 彻底失灵。动手前先确认 DOM 结构是否真的“干净”,比写对选择器更重要。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











