如何修复重启requestAnimationFrame循环后元素移动速度越来越快的问题
问题根源
- 事件重复绑定:你每次调用
move()方法都会执行一次initListener(),给DOM元素重复添加鼠标事件监听,每次触发鼠标操作都会多次执行pause()和move(),相当于同时运行多个动画实例,每帧的移动步长叠加,视觉上就是速度越来越快。 - 动画未清理就启动新实例:调用
move()前没有主动清理已有的动画帧任务,即使没有重复绑定问题,残留的未执行rAF任务也会和新启动的动画叠加,导致速度异常。 - 可选逻辑问题:
mouseout触发时调用this.move()没有传入要求的duration参数,可能引发意料外的逻辑错误。
修复方案
- 将
initListener()的调用从move()方法移动到initEl()方法末尾,保证整个实例生命周期只绑定一次事件,不会重复添加监听。 - 在
move()方法最开头先调用pause(),确保上一个动画实例被完全清理后再启动新动画。 - 给
move()的duration参数设置默认值,避免传参缺失报错。 - 可选优化:
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
相关产品推荐
相关产品推荐

