:has(> .submenu)无法触发展开动画,因其仅响应dom结构存在性(如子元素是否在html中),不响应display/height等样式变化;须将状态映射为:checked或aria-expanded等可检测特征,再配合max-height+opacity实现过渡动画。

为什么直接用 :has(> .submenu) 无法触发展开动画
因为 :has() 只能响应 DOM 结构变化(比如子元素存在与否),但树形菜单的“展开/收起”本质是切换 .submenu 的 display 或 height,而这些 CSS 属性变化不会触发 :has() 重新计算——它只看 HTML 是否含匹配子节点,不看样式是否生效。
所以常见错误是写成这样,却没效果:
.menu-item:has(.submenu) {
/* 以为 submenu 存在就自动展开,实际不会 */
}
真正可行的路径只有一条:把“是否展开”的状态映射到一个**可被 :has() 检测的 DOM 特征上**,比如 checked 状态、aria-expanded 属性,或更直接的——用隐藏的 <input type="checkbox"> 控制。
用隐藏 checkbox + :has(:checked) 驱动展开
这是目前最稳定、无需 JS、支持过渡动画的纯 CSS 方案。核心逻辑是:点击菜单项时 toggle checkbox,再用 :has(:checked) 选中父容器,从而控制子菜单显隐和动画。
- 必须把
<input>放在.menu-item内部,且紧邻.submenu前面(或用兄弟选择器定位) -
.submenu初始设为height: 0; overflow: hidden;,避免内容溢出 - 过渡只能作用于
height、opacity、transform等可动画属性;display不可过渡,不能用 - Chrome 105+、Safari 15.4+、Firefox 121+ 支持
:has();旧版 Safari 需加-webkit-前缀(但部分版本仍有 bug)
最小可用示例:
<label class="menu-item">
<input type="checkbox" class="toggle"><span class="label">文件</span>
<ul class="submenu">
<li>新建</li>
<li>打开</li>
</ul></label>
.submenu {
height: 0;
opacity: 0;
overflow: hidden;
transition: height 0.2s ease, opacity 0.2s ease;
}
.menu-item:has(.toggle:checked) .submenu {
height: auto;
opacity: 1;
}
/* 但 height: auto 无法过渡 → 需配合 max-height 代替 */
为什么必须用 max-height 替代 height: auto
因为 CSS 过渡不支持 height: auto——浏览器无法计算“auto”对应的像素值,导致动画跳变或失效。解决方案是用足够大的固定 max-height 值兜底。
- 设
max-height: 0初始状态,max-height: 500px展开状态(需大于所有可能子菜单高度) - 同时保留
overflow: hidden,否则超长内容会撑开容器 - 如果菜单层级深、子项数量不可控,500px 可能不够;此时建议改用
transform: scaleY(0)+opacity组合,更可靠 - 注意:Safari 对
max-height过渡有渲染延迟,偶尔首帧卡顿
修正后的关键样式:
.submenu {
max-height: 0;
opacity: 0;
overflow: hidden;
transition: max-height 0.25s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.25s ease;
}
.menu-item:has(.toggle:checked) .submenu {
max-height: 500px;
opacity: 1;
}
移动端点击穿透与可访问性补漏点
纯 CSS 树形菜单在移动设备上容易出现点击无响应,根本原因是 <label></label> 包裹了 <input>,但 Safari 对 label 内嵌 input 的点击区域识别不稳定;另外屏幕阅读器无法感知 aria-expanded 状态。
- 给
.toggle加position: absolute; opacity: 0; pointer-events: none;,再用label的::before伪元素模拟展开箭头并绑定点击 - 手动同步
aria-expanded:虽然 CSS 无法设置属性,但可通过[aria-expanded="true"]选择器做样式反馈,前提是 HTML 初始就写对 - 键盘用户需要
Tab聚焦到菜单项并按Space切换 —— 这点纯 CSS 做不到,必须加role="button"和 JS 监听,所以严格意义下“完全无 JS 的可访问树形菜单”目前不存在 - 如果项目要求 WCAG 2.1 AA,建议只用
:has()做视觉增强,核心交互仍由 JS 控制aria-expanded和 class 切换
真正上线时,别只盯着 :has() 多酷,先确认目标浏览器覆盖率、是否允许 JS 回退、以及无障碍测试能否过 VoiceOver/NVDA。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











