transitionend 在 css 过渡动画最后一帧完成时触发,非开始或样式生效时;若过渡被取消、中断或无视觉变化则不触发,且需兼容 webkit 前缀。

transitionend 事件到底在什么时候触发
它只在 CSS transition 真正执行完一帧动画后触发,不是“开始过渡时”,也不是“样式最终生效时”。如果过渡被取消(比如 class 被快速移除)、中断(新 transition 覆盖旧的),或者压根没产生视觉变化(如从 opacity: 1 到 opacity: 1),transitionend 就不会触发。
常见错误现象:
- 绑定了 transitionend 却始终收不到回调 → 检查是否真有可过渡的属性在变,且浏览器计算出的起始/结束值不同
- 多次快速 toggle class 导致事件漏发或错乱 → 过渡被新声明覆盖,旧的 end 事件被丢弃
- 使用了 transition: all 0.3s,但某些属性不可过渡(如 display)→ 对这些属性的变化不触发 transitionend
监听 transitionend 必须处理 vendor 前缀
老版本 Safari、旧版 Android Webview 仍需监听 webkitTransitionEnd。现代 Chrome/Firefox/Edge 已统一支持标准 transitionend,但兼容性兜底不能省。
实操建议:
- 不要只写 el.addEventListener('transitionend', handler)
- 用 ['transitionend', 'webkitTransitionEnd'] 双注册,或封装一个简单适配函数
- 注意:同一过渡可能触发多次事件(比如同时改了 opacity 和 transform),event.propertyName 可区分是哪个属性结束
示例:
function onTransitionEnd(el, callback) {<br> const handle = (e) => {<br> if (e.target === el && e.propertyName !== 'all') {<br> callback(e);<br> el.removeEventListener('transitionend', handle);<br> el.removeEventListener('webkitTransitionEnd', handle);<br> }<br> };<br> el.addEventListener('transitionend', handle);<br> el.addEventListener('webkitTransitionEnd', handle);<br>}
transitionend 不等于 DOM 更新完成
它只表示 CSS 过渡动画帧结束,不代表布局重排(reflow)或绘制(paint)已完成,更不保证后续 JS 操作能立刻读到最新渲染状态。
立即学习“前端免费学习笔记(深入)”;
使用场景中容易踩的坑:
- 在 transitionend 回调里立即调用 getBoundingClientRect() → 可能拿到过渡中途的中间值,尤其在高负载设备上
- 动画结束后立刻 removeChild 或隐藏元素 → 极小概率因渲染管线未同步导致闪一下
- 依赖 transition 后的尺寸做后续定位计算 → 应该用 requestAnimationFrame 包一层再读
性能影响:
- 频繁触发 transitionend(比如列表项逐个动画)可能堆积事件回调,建议节流或用事件委托
- 不要在回调里强制同步 layout(如读 offsetTop 后立刻改 style),会引发额外重排
用 transitionend 实现「动画完成才执行逻辑」的可靠写法
核心是:绑定 → 等待 → 清理 → 防重复。很多人忽略清理,导致同一元素多次动画时回调被反复执行。
关键点:
- 动画开始前先清掉旧监听器,避免残留
- 用 event.target 和 event.propertyName 双重校验,防止父元素冒泡干扰
- 如果过渡时间动态计算(比如根据数据长度算 duration),务必确保 JS 设置的 transition-duration 已生效再触发动画类
- 对于 height: 0 → auto 这类无效过渡,别指望 transitionend,得用 scrollHeight + requestAnimationFrame 手动模拟
容易被忽略的地方:CSS 中 transition-delay 会影响 transitionend 触发时机,但事件对象里没有 delay 信息,只能靠你自己记。










