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

如何修复重启requestAnimationFrame循环后元素移动速度越来越快的问题

问题根源
  • 事件重复绑定:你每次调用move()方法都会执行一次initListener(),给DOM元素重复添加鼠标事件监听,每次触发鼠标操作都会多次执行pause()和move(),相当于同时运行多个动画实例,每帧的移动步长叠加,视觉上就是速度越来越快。
  • 动画未清理就启动新实例:调用move()前没有主动清理已有的动画帧任务,即使没有重复绑定问题,残留的未执行rAF任务也会和新启动的动画叠加,导致速度异常。
  • 可选逻辑问题:mouseout触发时调用this.move()没有传入要求的duration参数,可能引发意料外的逻辑错误。
修复方案
  1. 将initListener()的调用从move()方法移动到initEl()方法末尾,保证整个实例生命周期只绑定一次事件,不会重复添加监听。
  2. 在move()方法最开头先调用pause(),确保上一个动画实例被完全清理后再启动新动画。
  3. 给move()的duration参数设置默认值,避免传参缺失报错。
  4. 可选优化:mouseenter和mouseover事件功能重叠,删除其中一个绑定即可,避免重复触发pause逻辑。
修改后核心代码
type state = "init" | "mount" | "moving" | "pause" | "end";

interface position {
  left: number;
  top: number;
}

interface elProps {
  id: string;
  text: string;
  avatar: string;
}

export default class Bullet {
  public el: HTMLElement;
  public targetEl: HTMLElement;
  public unit: string = "px";
  private id:string;
  private state: state;
  private position: position;
  private animationId: number | undefined;
  constructor(props: elProps) {
    if (!props) return;
    this.el = document.createElement("div");
    this.initEl(props);
  }
  initEl(props: elProps) {
    const defaultStyle = {};
    this.el.style["height"] = "40px";
    this.el.style["background"] = "lightblue";
    this.el.style["display"] = "inline-flex";
    this.el.style["position"] = "absolute";
    
    const { text, avatar, id } = props;
    this.el.setAttribute("id", id);
    this.id = id
    const textEl = document.createElement("div");
    textEl.innerText = text;
    const avatarEl = document.createElement("img");
    avatarEl.setAttribute("src", avatar);
    
    this.el.appendChild(avatarEl);
    this.el.appendChild(textEl);
    
    this.state = "init";
    // 仅在初始化时绑定一次事件
    this.initListener();
  }
  mount(targetEl: HTMLElement, position: position, unit: string) {
    this.position = position;
    this.unit = unit;
    this.targetEl = targetEl;
    this.el.style["left"] = this.position.left + this.unit;
    
    targetEl.appendChild(this.el);
    
    this.state = "mount";
    const domEl = document.querySelector(`#${SPECIAL_ID}`);
    if (!domEl) {
      throw new Error("mount bullet error");
    }
    this.el = domEl as HTMLElement;
  }
  // 给duration加默认值,避免调用时不传参报错
  move(duration: number = 3000) {
    // 启动新动画前先清理旧动画
    this.pause();
    this.state = "moving";

    const animation = () => {
      if (this.state !== "moving") return;

      this.position.left -= 1.2;
      this.el.style["left"] = this.position.left + this.unit;
      this.animationId = requestAnimationFrame(animation);
    };
    animation();
  }
  pause() {
    if(this.animationId){
      cancelAnimationFrame(this.animationId)
      this.animationId = undefined
    }
  }
  initListener() {
    this.el.addEventListener("mouseenter", () => {
      this.state = "pause";
      this.pause()
    });
    // 可选:删除mouseover的重复绑定
    // this.el.addEventListener("mouseover", () => {
    //   this.state = "pause";
    //   this.pause()
    // });
    this.el.addEventListener("mouseout", () => {
      this.state = "moving";
      this.move()
    });
  }
}

内容的提问来源于stack exchange,提问作者Livio

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 08:45:04