用 scrollHeight + 强制重排实现 max-height 过渡动画最可靠:收起时设 max-height 为 scrollHeight→读 offsetHeight 触发重排→设为 0;展开时反之;需加 overflow: hidden 并避免 height 动画。

直接用 max-height 做过渡动画看似简单,但实际容易失效——根本原因在于浏览器无法对 none 或 auto 这类非数值值做插值计算。真正平滑、可靠、兼容性好的方案,是结合 JS 动态读取真实高度 + CSS max-height 过渡,并严格控制重排时机。
用 scrollHeight + 强制重排触发过渡
关键不是“设个值就完事”,而是让浏览器在两次样式变更之间完成一次同步布局计算(即重排),否则会合并操作、跳过动画。
- 收起时:先设
max-height为当前scrollHeight,再立即读取offsetHeight触发重排,最后设max-height: 0 - 展开时:先设
max-height: 0,再读取offsetHeight,最后设为scrollHeight + 'px' - 务必加
overflow: hidden,否则内容可能在动画中途溢出显示
避免 height 动画,坚持用 max-height
height 表面直观,但问题更多:它受 padding、border、字体响应式变化影响大,计算稍有偏差就会留白或裁切;而 max-height 只设上限,不影响内部流式布局,容错性强。
- 在 flex 或 grid 容器中,
height常被min-height或align-items干扰,max-height更稳定 - 目标值不必完全精确——只要 ≥ 实际高度即可,略高一点不影响视觉,还能避免因四舍五入导致的微小抖动
提升性能与体验的细节优化
长列表或频繁切换场景下,这些细节能明显减少卡顿和闪烁:
- 给元素加
will-change: max-height,提示浏览器提前升层,尤其在 iOS Safari 和低端安卓机上效果明显 - 过渡时间建议 0.25s–0.35s,缓动函数用
cubic-bezier(0.4, 0, 0.2, 1)(类似 Material 的标准减速曲线) - 按钮文案和
aria-expanded属性必须同步更新,保证可访问性;按钮不要放在截断容器内部,防止展开后被新内容遮挡
不推荐的“捷径”写法
以下做法看似省事,但会在不同设备或内容长度变化时暴露问题:
-
max-height: 0 → max-height: 500px(写死固定值):内容短时留白,内容长时裁切 -
max-height: 0 → max-height: none:none不可动画,直接跳变 - 用
clientHeight或offsetHeight替代scrollHeight:它们不含溢出部分,读不到完整内容高度


















