transitionend 被触发多次的根本原因是每个可过渡的 CSS 属性变化都会独立触发一次事件;WebKit 内核甚至对同一属性正向/反向动画各触发一次;需用 event.propertyName 过滤、避免 transition: all、防重复绑定监听器,并推荐改用 animationend 替代。

transitionend 为什么会被触发多次
根本原因是:每个可过渡的 CSS 属性变化,都会独立触发一次 transitionend。比如你同时改了 transform 和 opacity,事件就至少触发两次;如果还动了 color 或 border-color,次数继续叠加。WebKit 内核(如 Safari、旧版 iOS WebView)甚至会对同一属性在正向/反向动画中各触发一次——.box.active 加上又去掉,可能触发四次。
只响应目标属性:用 event.propertyName 过滤
监听时不做判断,等于默认接受所有属性变更的回调,极易导致逻辑错乱(比如 height 动画还没完,opacity 先结束就把元素隐藏了)。
- 必须检查
e.propertyName,只处理你关心的那个属性,例如:if (e.propertyName === 'transform') - 避免用
all做 transition 源头,它会让所有变更属性都参与过渡,放大重复触发风险 - 如果多个属性需协同完成(如
transform+opacity),建议统一用一个“锚点属性”作为完成信号,比如选transform—— 它更稳定、更少受伪类或继承干扰
防重复绑定和残留监听器
常见错误是每次点击都 addEventListener('transitionend', handler),但忘了在动画开始前或结束后 removeEventListener。尤其在快速连续操作(如连点按钮)时,监听器越积越多,回调执行次数呈指数增长。
- 绑定前先清理:用
el.removeEventListener('transitionend', handler)确保旧监听器已移除 - 推荐封装成单次有效模式:在 handler 里第一行就
el.removeEventListener('transitionend', handler) - 如果用 class 切换触发动画,确保 class 添加和监听器注册在同一次 JS 执行流里,避免因异步导致监听器滞后于动画启动
更稳妥的替代方案:改用 CSS Animation + animationend
当业务逻辑强依赖“动画真正结束”这个信号时,transitionend 天然不可靠。CSS Animation 的 animationend 只在整段关键帧播放完毕后触发一次,且不受子元素样式干扰,animation-fill-mode: forwards 还能固化终态。
立即学习“前端免费学习笔记(深入)”;
- 把原来写在
:hover或 class 里的transition拆成@keyframes,例如定义slide-in和slide-out - 用
el.style.animation = 'slide-in 0.3s ease-out'触发,监听animationend即可 - 注意:不要混用
transition和animation控制同一元素的同一属性,否则行为不可预测
transitionend 就得配上 propertyName 判断 + 显式移除监听器,否则迟早出问题。


















