正确做法是将 :has() 写在网格容器上以动态切换 grid-template-columns,而非子项上;需确保 .featured 是直系子元素,且 :not(:has()) 规则置于后面;跨列响应需避免 @media 内嵌 :has(),并注意文本节点干扰 nth-child 计数。

grid-template-columns 里用 :has() 控制跨列,但必须写在父容器上
直接在子项上写 :has() 不会改变父容器的列定义——它只匹配元素,不触发重排。真正起作用的是让网格容器自己根据子项数量或特征切换 grid-template-columns。
常见错误是这样写:.item:has(+ .featured) 然后试图在里面改 grid-column: span 2,结果无效。因为 grid-column 是子项自己的属性,:has() 在子项上只能选中它,不能反向控制父级列结构。
正确姿势是把判断逻辑放在容器上:
.grid {
display: grid;
gap: 1rem;
}
.grid:has(.featured) {
grid-template-columns: repeat(2, 1fr);
}
.grid:not(:has(.featured)) {
grid-template-columns: repeat(3, 1fr);
}
注意两点:一是 .featured 必须是 .grid 的**直系子元素**;二是规则顺序不能颠倒,:not(:has()) 要放后面,否则会被覆盖。
:has() + nth-child 组合实现“第3项之后全跨两列”
想让前两个子项正常占一列,从第三个开始每个都跨两列?不能靠 .grid > *:nth-child(n+3) { grid-column: span 2 } 单独生效——这没问题,但若你还想动态响应子项增删(比如删掉第2项后,原第3项变成第2项,不该再跨列),就得用 :has() 做存在性兜底。
实操建议:
- 用
.grid:has(> *:nth-child(3))表示“至少有3个子项”,此时才启用跨列规则 - 配合
.grid > *:nth-child(n+3)设置grid-column: span 2,但仅当容器满足前述条件时才生效 - 避免写成
.grid:has(> *:nth-child(3)) > *:nth-child(n+3)—— 这种嵌套在部分 Safari 版本中解析失败
更稳的写法是拆开:
.grid:has(> *:nth-child(3)) {
--has-third: 1;
}
.grid > *:nth-child(n+3) {
grid-column: span calc(var(--has-third, 0) * 2 + 1);
}
不过这种 CSS 变量+calc 方案依赖运行时计算,不如纯 :has() 规则可靠;真要严谨控制,还是推荐用独立规则块。
跨列 + 响应式断点混用时,:has() 规则别嵌在 @media 里
Chrome 105–112 和 Safari 16.4–17.0 对 @media 内部的 :has() 支持不稳定——规则可能被整个忽略,且不报错。例如下面这段不会生效:
@media (min-width: 768px) {
.grid:has(.featured) {
grid-template-columns: 1fr 2fr;
}
}
替代方案是把媒体查询和 :has() 平级叠加:
.grid:has(.featured) {
grid-template-columns: 1fr;
}
@media (min-width: 768px) {
.grid:has(.featured) {
grid-template-columns: 1fr 2fr;
}
.grid:not(:has(.featured)) {
grid-template-columns: repeat(3, 1fr);
}
}
要点:
- 所有
:has()规则必须处于最外层样式表作用域 - 如果同时依赖屏幕宽度和子项特征,优先按“子项特征”分组,再在每组内按媒体查询细分
- Firefox 121+ 对嵌套
:has()更宽容,但为保兼容,统一按外层写
子项含文本节点时 :has() 计数失效,得先清理 DOM 结构
:has(> *:nth-child(3)) 只匹配**元素节点**,遇到换行、空格生成的文本节点就跳过。比如这个 HTML:
<div class="grid"> <div>A</div> <p></p> <div>B</div> <div class="featured">C</div> </div>
中间的换行会产生一个文本节点,导致 .featured 实际是第 4 个子节点(text、div、text、div),:has(> *:nth-child(3)) 就不匹配。
解决办法只有两个:
- 用
display: contents把文本节点“压平”,但注意这会让伪元素(如::before)失效 - JS 初始化时用
element.replaceChildren(...)清理空白节点,确保子项全是元素节点
没有纯 CSS 的兜底方案。一旦 DOM 里混入不可控的富文本或 CMS 输出,:has() 的数量判断就容易失准——这点比 JS 的 children.length 严格得多,也更容易被忽略。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











