必须用唯一标识作key(如item.id)并等待transitionend事件再删除DOM,同时用可计算高度+overflow:hidden实现平滑收起动画,避免用索引key或直接removeChild。

删除前必须给元素加 key,否则 Vue/React 动画失效
框架里列表动画依赖 key 识别节点身份。如果用索引 :key="index",删除中间项时后续项的 key 不变,框架会复用 DOM 而非触发移除动画。真实场景中应使用唯一标识,比如 :key="item.id"。
纯 HTML/CSS 也一样:动画靠元素存在与否触发,但若直接 removeChild() 或 display: none 后立刻删节点,过渡根本来不及播。得等 transitionend 事件再清理 DOM。
- Vue 用户检查是否用了稳定
key,别用v-for="(item, i) in list" :key="i" - React 用户同理,避免
map((x, i) => <li key="{i}">) - 原生 JS 删除前先加类(如
is-leaving),在transitionend回调里调用el.remove()
height + overflow 过渡最稳妥,别只靠 opacity
仅用 opacity: 0 配合 transition 看似简单,但元素仍占布局空间,可能造成下方内容“突然上跳”,体验断裂。真正平滑的收起动画要收缩高度并隐藏溢出。
关键点是:目标高度必须可计算。不能用 height: auto 过渡(浏览器不支持),得先读取当前 offsetHeight,再设为具体像素值,最后过渡到 0。
立即学习“前端免费学习笔记(深入)”;
- 先
el.style.height = el.offsetHeight + 'px' - 再
el.classList.add('is-collapsing')(该类含height: 0; overflow: hidden;) - CSS 中写
transition: height 0.3s ease-out, opacity 0.3s ease-out - 注意:
box-sizing: border-box要一致,否则高度计算偏差
用 getComputedStyle 读取初始状态,避开重排陷阱
直接读 el.style.height 拿不到 CSS 规则里的值,而 offsetHeight 又会强制触发重排(layout),高频操作下卡顿明显。正确做法是先让元素进入“可测量状态”,再用 getComputedStyle 安全取值。
例如:删除前临时加 visibility: hidden; position: absolute;(不占位、不触发布局),再读 computed.height,之后恢复样式并开始动画。
- 避免在循环里反复读
offsetHeight,尤其列表长时 - 如果用
ResizeObserver监听尺寸变化,它本身不触发重排,但回调里读offsetHeight仍会 - 简单场景下,用
requestAnimationFrame包一层读取,把重排合并到下一帧
IE11 和 Safari 对 transition 的兼容细节不能忽略
IE11 不支持 transition 作用于 height 从 auto 到具体值的动画;Safari 旧版本对 transform: scaleY(0) 过渡有渲染残留。这些坑在开发机上看不出来,上线后才暴露。
- IE11 方案:改用
max-height(设一个足够大的值如500px),过渡max-height和opacity - Safari 方案:加
transform: translateZ(0)强制 GPU 加速,或用scaleY(0.001)替代0 - 所有过渡属性必须带浏览器前缀:
-webkit-transition、-ms-transition(尽管现代项目可能已不需)
transitionend 再清理 DOM —— 很多人删完就 el.remove(),结果动画只播了一半。还有就是 key 的稳定性,看似小事,却是框架动画能否触发的第一道关卡。



















