You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaScript onanimationend 异常行为的原因分析与修复方案

问题复现代码

JavaScript

const highlight = (element) => {
  element.classList.remove('highlight');
  setTimeout(() => {
    element.classList.add('highlight');
  }, 0);
  element.onanimationend = () => {
    element.classList.remove('highlight');
  }
}
document.querySelector('body').addEventListener('click', event => {
  if (event.target.hash) {
    event.preventDefault();
    const element = document.querySelector(event.target.hash);
    highlight(element);
  }
});

CSS

@keyframes highlight {
  from {
    background: red;
  }
  to {
    background: white;
  }
}
.highlight {
  animation: highlight 5s;
}

HTML

<ul>
  <li>
    <a href="#foo">Foo</a>
    <div>
      <ul>
        <li>
          <a href="#foo-1">Foo 1</a>
        </li>
        <li>
          <a href="#foo-2">Foo 2</a>
        </li>
        <li>
          <a href="#foo-3">Foo 3</a>
        </li>
      </ul>
    </div>
  </li>
  <li>
    <a href="#bar">Bar</a>
  </li>
  <li>
    <a href="#baz">Baz</a>
  </li>
</ul>

<hr>

<div id="foo">
  <h2>Foo</h2>
  <ul>
    <li id="foo-1">Foo 1</li>
    <li id="foo-2">Foo 2</li>
    <li id="foo-3">Foo 3</li>
  </ul>
</div>

<div id="bar">
  <h2>Bar</h2>
</div>

<div id="baz">
  <h2>Baz</h2>
</div>

问题原因

核心问题是CSS动画事件会冒泡,再加上你用了element.onanimationend这种赋值式的事件绑定:

  1. 点击Foo 1后,它的动画启动,同时绑定了onanimationend回调;
  2. 3秒后点击Foo,Foo元素也绑定了自己的onanimationend回调;
  3. 再过2秒,Foo 1的动画结束,触发它的onanimationend事件,这个事件会冒泡到父元素Foo;
  4. 因为Foo的回调没有判断事件来源,直接执行了移除highlight类的操作,导致Foo的动画被提前终止。

修复方案

以下两种方式任选其一即可:

方案1:在回调里判断事件目标是否为当前元素

修改highlight函数,只在当前元素自己的动画结束时才移除类:

const highlight = (element) => {
  element.classList.remove('highlight');
  setTimeout(() => {
    element.classList.add('highlight');
  }, 0);
  element.onanimationend = (event) => {
    // 仅处理当前元素触发的动画结束事件
    if (event.target === element) {
      element.classList.remove('highlight');
    }
  }
}

方案2:用addEventListener绑定并清理旧监听

这种方式更严谨,避免残留监听或重复绑定的问题:

const highlight = (element) => {
  // 先移除之前的监听,避免冲突
  element.removeEventListener('animationend', handleAnimationEnd);
  
  element.classList.remove('highlight');
  setTimeout(() => {
    element.classList.add('highlight');
  }, 0);

  function handleAnimationEnd(event) {
    if (event.target === element) {
      element.classList.remove('highlight');
      // 动画结束后移除监听,避免内存泄漏
      element.removeEventListener('animationend', handleAnimationEnd);
    }
  }
  
  element.addEventListener('animationend', handleAnimationEnd);
}

内容的提问来源于stack exchange,提问作者Three Year Old

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 17:44:51