transition 无法直接作用于 height: auto,因浏览器无法对“auto”插值;需设 height: 0 → 具体高度,配合 overflow: hidden 和 aria-expanded 保障可访问性,Safari 需 setTimeout 兜底。

transition 无法直接作用于 height: auto
这是最常卡住的地方:你给 height 写了 transition: height 0.3s,但元素展开时却“啪”一下跳出来,没有过渡。因为浏览器无法对 height: auto 做插值计算——它不知道“auto”具体是多少像素。
解决思路不是绕开,而是让浏览器有明确的起止值:
- 展开前设
height: 0,同时用overflow: hidden隐藏溢出内容 - 展开后设具体高度(比如
height: 240px),或用 JS 动态读取scrollHeight后赋值 - 避免用
max-height代替height——它虽能过渡,但需预估足够大的值,否则会截断或过渡不自然
用 getBoundingClientRect().height 替代 offsetHeight 更可靠
想让折叠动画精准贴合内容高度?别直接用 offsetHeight,它可能包含边框、滚动条占位,且在 display: none 下返回 0。
更稳妥的做法是临时切换为 position: absolute; visibility: hidden; display: block,再读取 getBoundingClientRect().height:
立即学习“前端免费学习笔记(深入)”;
const el = document.querySelector('.foldable');
el.style.position = 'absolute';
el.style.visibility = 'hidden';
el.style.display = 'block';
const targetHeight = el.getBoundingClientRect().height;
el.style.cssText = ''; // 恢复原样式
这个技巧在 React/Vue 的 ref 回调或 useEffect 里也适用,关键是确保元素已渲染、无隐藏干扰。
折叠后保持可访问性(a11y)不能只靠 display: none
单纯用 display: none 或 visibility: hidden 会让屏幕阅读器完全忽略内容,对键盘导航也不友好。
正确做法是组合控制:
- 折叠状态:设
height: 0+overflow: hidden+aria-expanded="false" - 展开状态:恢复
height+aria-expanded="true" - 按钮本身加
aria-controls="id-of-content",指向目标区域 ID - 避免用
pointer-events: none锁定内容——它会阻断焦点和键盘操作
移动端 Safari 的 transitionend 事件可能不触发
尤其在快速连续点击折叠/展开时,Safari 有时不会发出 transitionend,导致状态卡住(比如高度设为 0 后没清掉内联 height 样式)。
保险策略是加兜底计时器:
el.addEventListener('transitionend', handleTransitionEnd);
setTimeout(() => {
if (el.style.height === '0px') {
el.style.height = '';
}
}, 350); // 略长于 CSS 中定义的 transition 时长
更彻底的方案是改用 requestAnimationFrame + getComputedStyle 轮询检测高度变化,但多数场景下 setTimeout 已足够。
真正麻烦的是异步内容加载后的折叠——比如展开时才请求 API 渲染列表,这时高度是动态增长的,必须等 DOM 更新完成再触发动画,offsetHeight 读早了就是 0。










