纯 css 难以可靠实现多级手风琴平滑动效,因 :checked 无法跨层级控制子菜单、details 不支持 transition 且嵌套不联动,max-height 动画需预设固定值并避免混用 display: none。

纯 CSS 无法可靠实现「多级」手风琴的平滑伸缩动效——因为 :checked 只能控制直属兄弟/后续兄弟元素,无法跨层级影响子菜单的 max-height;而 details 元素不支持 transition,且嵌套时 open 状态不联动。
为什么多级手风琴用纯 CSS 很难做动画
常见错误是试图用 :checked ~ .submenu 控制二级内容,但若结构是「一级 checkbox → label → .content → 二级 checkbox」,二级 checkbox 就不在一级的后续兄弟流中,~ 或 + 选择器根本选不到它。浏览器不会回溯父容器再向下匹配子元素。
另一个坑是强行给 details 套 transition:哪怕写 details { transition: height 0.3s; },open 属性切换仍不触发重排,动画完全静止。Safari 和 Chrome 表现一致,这不是 bug,是规范行为。
列表项里混用 display: none 和 max-height: 0 也容易出问题:前者跳过布局计算,后者依赖 layout,两者混用会导致过渡中断或高度突变。
能走通的纯 CSS 多级方案只有两种结构
第一种:所有层级都用独立 input[type="checkbox"],靠 HTML 结构保证每个子菜单紧邻其开关控件——也就是「扁平化嵌套」:
- 一级
<input id="a">+<label for="a"></label>+<div class="level1"> <li>在 <code>.level1内部直接放二级<input id="a-b">+<label for="a-b"></label>+<div class="level2"> <li>每个 <code>.levelN都设max-height: 0和overflow: hidden,:checked + label + .levelN设足够大的max-height(如500px) <details><summary>一级</summary><details><summary>二级</summary>内容</details></details>- 注意 Safari 对
summary::marker自定义支持不全,箭头旋转需额外 hack - IE 完全不支持,需降级为 JS 方案
- 测出所有可能内容的最大行数,按行高 × 行数 + padding 估算,比如
max-height: 480px - 若内容高度差异极大(如 FAQ 含长表格),宁可设
max-height: 1200px,避免视觉裁切 - 过渡属性必须显式写全:
transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1),漏掉 timing-function 会退化成 linear - 别在
:checked规则里同时写opacity和max-height过渡——不同属性帧率可能不同步,造成闪烁 - 点击时获取目标
.submenu的scrollHeight,设为max-height - 监听
transitionend,结束后清空max-height(保留overflow: hidden) - 互斥逻辑(关其他项)用
querySelectorAll遍历即可,比 CSS 伪类灵活得多
第二种:放弃动画,用原生 details 嵌套,语义和可访问性最优,但展开收起是硬切,无过渡:
max-height 动画必须预设“足够大”的值
不能写 max-height: auto——CSS 不支持对 auto 做 transition;也不能只设 max-height: 200px 就完事,内容超长会被截断。
实操建议:
真正需要多级动画时,JS 是更现实的选择
纯 CSS 的代价是结构僵硬、维护成本高、响应式适配困难。而现代轻量 JS(Element.scrollHeight + requestAnimationFrame)能动态读取真实高度,且只需几行代码:
容易被忽略的一点:多级菜单的焦点管理。纯 CSS 方案无法自动 focus() 到展开后的内容首元素,键盘用户会卡在标题上——这已经不是动效问题,而是无障碍底线。











