最简判断方式是 .card:has(img),仅匹配含 标签的卡片,不感知 background-image、 或 ;不支持交互伪类、跨级选择器及 @media 内嵌套;safari 15.4+ 支持;动态插入图片时自动响应 dom 变化。

用 :has() 检测卡片内是否有 <img> 元素
直接写 .card:has(img) 就能命中含图片的卡片,这是最简判断方式。注意它不匹配 <picture></picture> 或 <svg></svg>,只对真实渲染的 <img> 标签生效(哪怕 src 404 或为空,只要标签存在就算)。如果卡片里图片是通过 background-image 设置的,:has() 完全感知不到——CSS 无法检测 CSS 属性值。
:has() 嵌套层级与伪类组合限制
不能写 .card:has(img:hover) 这类带交互伪类的组合,浏览器会直接忽略整条规则;也不能跨父级写 .card:has(.content img)(除非 .content 是 .card 的直系子元素)。常见误写:.card:has(> img) —— 这其实等价于 .card:has(img),因为 :has() 内部的后代选择器默认就是宽松匹配,加不加 > 不影响结果,但加了反而可能因层级理解偏差导致漏匹配。
切换布局时避免触发重排或样式冲突
用 :has() 切换布局,优先改 display、grid-template 或 flex-direction 这类不触发重排的属性:
.card { display: flex; flex-direction: column; }.card:has(img) { flex-direction: row; }
别在 :has() 规则里改 width、height 或 margin,尤其当卡片是 Grid 子项时,这些改动可能和容器的 track sizing 冲突,造成布局抖动。另外,Safari 15.4+ 才支持 :has(),且目前不支持在 @media 内部嵌套使用,所以响应式 + :has() 组合要拆成独立媒体查询块。
替代方案:当 :has() 不可用或需兼容旧浏览器
服务端渲染时可直接加 class:<div class="card card--has-img">;客户端 JS 方案只需一行:
<pre class="brush:php;toolbar:false;">document.querySelectorAll('.card').forEach(el => {
el.classList.toggle('has-img', el.querySelector('img') !== null);
});</pre>
<p>然后用 <code>.card.has-img 写 CSS。比依赖 :has() 更稳,也方便后续加 lazyload 状态(比如 .card.has-img.loaded)。真正麻烦的是动态插入图片的场景——JS 需监听 MutationObserver,而 :has() 会自动响应 DOM 变化,这点它确实更省心。
记住::has() 是声明式逻辑,但它的“感知”仅限于当前时刻的 DOM 结构,不关心资源加载状态,也不处理 CSS-in-JS 注入的样式规则。











