height: 0 → height: auto加transition一定失效,因浏览器无法对auto做数值插值,transition需明确起止像素值才能逐帧计算;唯一有效路径是固定值→固定值,如max-height: 0 → max-height: 500px,配合overflow: hidden、opacity及visibility控制显隐,并仅对max-height和opacity设置transition。

为什么height: 0 → height: auto加transition一定失效
浏览器无法对auto做数值插值——transition需要明确的起始值和结束值才能逐帧计算中间状态。写height: auto等于告诉浏览器“你看着办”,它没法“看着办”出过渡帧。
常见错误现象:transition: height 0.3s + height: 0/height: auto,结果菜单要么瞬间闪现,要么完全不动。
真正能动效的路径只有一条:固定像素值 → 固定像素值(比如max-height: 0 → max-height: 500px)。
用max-height替代height的最小可行配置
这是最轻量、兼容性最好、无需 JS 的方案,核心是借max-height的可动画性 + overflow: hidden隐藏溢出内容。
立即学习“前端免费学习笔记(深入)”;
- 初始状态:
max-height: 0+overflow: hidden+opacity: 0+visibility: hidden - 展开状态:
max-height: 500px+opacity: 1+visibility: visible - 只对可插值属性写
transition:transition: max-height 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94), opacity 0.3s - 绝不要写
transition: all 0.3s——万一菜单里有color或font-size变化,会意外触发无关动画
JS 控制时,为什么不能直接操作style.height
哪怕你用 JS 触发展开,也绝不要写el.style.height = '200px'——这会覆盖 CSS 中定义的transition,动画直接报废。
正确做法始终是只操作 class:
el.classList.toggle('is-open')- 所有样式和过渡逻辑全留在 CSS 里
- 如果用
<input type="checkbox">模拟开关,确保它用position: absolute; opacity: 0; width: 1px; height: 1px;隐藏,而非display: none(否则屏幕阅读器不可见) - 移动端点击热区至少 44×44px,给关联的
<label></label>加cursor: pointer和足够padding
max-height设多大才不卡顿又不截断
设500px是常见经验值,但它不是万能解:得明显大于菜单最大可能高度(含padding、line-height),但别过大(影响重排性能);设太大,收起时明显拖尾;设太小,内容被截断。
真实内容高度不确定时,别硬猜——优先用scrollHeight动态设置:
- 展开前:
element.style.maxHeight = element.scrollHeight + 'px' - 收起时:先设
maxHeight为具体值,再监听transitionend事件清空(不是setTimeout),否则立即清空会跳变 - 示例清理逻辑:
menu.addEventListener('transitionend', () => { if(menu.style.maxHeight === '0px') menu.style.maxHeight = ''; });
缓动函数比时长更影响体验。cubic-bezier(0.25, 0.46, 0.45, 0.94)比默认ease起始响应更快、收尾更柔和,但很多人直接复制就用,忘了它依赖精确的 DOM 高度测量和 clean-up 时机——这两点一漏,动画就断在半路。


















