Chrome 105+ 才真正可用::has() 于2022年8月随Chrome 105稳定版首发支持;Safari 15.4仅部分支持,Firefox至今不支持;SSR中无效,性能开销大,慎用于高频交互场景。
:has()
不是所有标称“支持 CSS4”的浏览器都能用 :has(),它早在 2018 年就写进 CSS Selectors Level 4 草案,但直到 Chrome 105(2022 年 8 月)才首次落地为稳定版支持。Safari 15.4 仅支持部分简单用法(如 :has(+ p)),遇到嵌套或否定伪类会静默失效;Firefox 目前(v128)仍完全不支持,且无明确启用时间表。
所以别信“CSS4 已支持父选择器”这类模糊说法——实际得看具体浏览器版本和用法复杂度:
:has(> .active) 在 Chrome 105+ 可用,Safari 15.4+ 会忽略:has(:hover) 或 :has(:not(.disabled)) 属于高危组合,在 Safari 和旧 Chrome 中大概率不触发样式:has() 不会生效,需配合 useEffect 或 useLayoutEffect 做降级处理:has() 的性能代价比想象中大浏览器必须在子元素状态变化时反向遍历 DOM 树,重新评估所有含 :has() 的规则。这意味着:
div:has(p:hover) 规则,会让每个 <p> 的 hover 事件都触发父 <div> 的样式重计算section:has(div:has(p)) 会指数级放大开销,Lighthouse 可能直接标红“Avoid complex selectors”建议只在低频交互场景(如表单验证、模态框开关)中使用,避免放在 body 或无限滚动容器上。
立即学习“前端免费学习笔记(深入)”;
:has() 改用 JS不是所有“需要父级响应子状态”的场景都适合 :has()。以下情况直接上 JS 更稳:
:has() 就是不可用classList.toggle('error')),而非原生伪类(:hover, :focus)—— :has() 对 class 变化响应及时,但对某些动态插入节点的时机仍可能错过最小成本 JS 方案示例:
document.querySelectorAll('.child').forEach(el => { el.addEventListener('input', () => { el.closest('.parent')?.classList.toggle('has-error', el.checkValidity() === false); });});
:has() 写法想用又怕翻车?只保留这几种经过多版本验证的模式:
.card:has(> .card__header--hidden)
.form-group:has(+ .error-message)
nav:has(a[href='/contact']):not(:has(a[href='/admin']))(注意 :not() 必须放在最后)别碰 :has() 里套 :is()、:where() 或属性选择器中的正则(如 [data-id^="item-"]),这些在 Safari 16.4 之前全都不认。
真正麻烦的从来不是语法能不能写出来,而是你写的那行 :has() 在用户手机上 Safari 里到底有没有执行。