transitionend 是唯一可靠的监听方式,但需检查 event.propertyName、加防重入开关、避免 transition: all;不触发常见因元素被移除、样式重置或过渡未生效;应强制重排、确保 transition 写在常态 class,并封装函数及时清理监听。

transitionend 是唯一可靠的监听方式,但直接绑事件很容易出错——不是漏触发,就是重复触发,或者根本没反应。
为什么 transitionend 会触发多次?
当多个 CSS 属性同时发生过渡(比如 width、opacity、transform),浏览器会对每个可动画属性各派发一次 transitionend。你写一次 addEventListener('transitionend', ...),实际可能收到 3 次回调。
- 检查
event.propertyName,只响应你关心的属性,比如if (event.propertyName === 'transform') { ... } - 加个防重入开关:用
element.dataset.isTransitioning = 'true'标记,回调里清掉,避免重复逻辑执行 - 别用
transition: all 0.3s,明确列出要过渡的属性,比如transition: transform 0.3s, opacity 0.3s
为什么 transitionend 有时根本不触发?
常见原因就三个:元素被提前移除、样式被强制重置、或过渡压根没生效。
-
display: none或removeChild()在过渡中途执行 → 事件永远不触发。改用visibility: hidden+opacity: 0配合transition安全收尾 - JS 中连续修改样式(如先设
el.style.transform = 'scale(1)',再设el.style.transition = 'transform 0.3s')→ 浏览器来不及渲染,过渡被跳过。必须强制重排:el.offsetHeight再设 transition -
transition写在伪类(如:hover)里,但 JS 操作的是元素本身 → 确保 transition 规则写在元素的常态 class 上,而不是仅在伪类中
如何安全地监听并清理状态?
不要依赖单次绑定,尤其在反复 toggle 动画的场景下(比如抽屉菜单、模态框)。推荐封装一个最小可用函数:
立即学习“前端免费学习笔记(深入)”;
function onTransitionEnd(el, prop, callback) {
const handler = (e) => {
if (e.propertyName === prop) {
el.removeEventListener('transitionend', handler);
callback();
}
};
el.addEventListener('transitionend', handler);
}
- 传入具体属性名(如
'transform'),避免误响应其他过渡 - 事件处理完立刻
removeEventListener,防止内存泄漏或多次调用 - 如果需要兼容旧版 Safari,补上
webkitTransitionEnd,但现代项目基本可忽略
真正容易被忽略的点是:transition 是否真的启动了。哪怕写了 transition: all 0.2s,如果前后 computed style 完全一样(比如都为 opacity: 1),浏览器根本不会触发任何 transition 相关事件——得靠 getComputedStyle 对比确认值确有变化。


















