JavaScript动画队列类问题:后续动画失效且无法连续执行
DOM动画队列类问题修复
问题描述
我封装了一个用于DOM元素动画的JavaScript类AnimateElement,目的是实现动画的顺序执行,但遇到两个核心问题:
- 第一个动画执行完毕后,后续动画没有过渡效果,直接跳转到终点位置;
- 第二个动画之后的所有后续动画都无法执行。
查看控制台日志发现,animationend事件处理函数仅被调用一次——虽然处理函数内会移除自身的事件监听,但move方法会重新添加该监听,逻辑上看似没问题但实际不生效。
原代码如下:
export class AnimateElement { constructor(el) { this.el = el; this.animationQueue = []; this.handleAnimationEnd = this.handleAnimationEnd.bind(this); } addAnimation(fromx, fromy, tox, toy, delay, time, func = 'linear', direction = 'forwards', handler = null) { this.animationQueue.push({ fromx, fromy, tox, toy, delay, time, func, direction, handler }); console.log(this.animationQueue); } move() { console.log('move called.', this.animationQueue.length, this.animationQueue); if (this.animationQueue.length > 0) { let oper = this.animationQueue.shift(); let fromx = oper.fromx !== null ? oper.fromx : parseFloat(this.el.style.getPropertyValue('--translate-to-x')) || 0; let fromy = oper.fromy !== null ? oper.fromy : parseFloat(this.el.style.getPropertyValue('--translate-to-y')) || 0; console.log('fromx:', fromx, 'fromy:', fromy); this.el.style.setProperty('--translate-from-x', fromx + 'px'); this.el.style.setProperty('--translate-from-y', fromy + 'px'); this.el.style.setProperty('--translate-to-x', oper.tox + 'px'); this.el.style.setProperty('--translate-to-y', oper.toy + 'px'); this.el.style.setProperty('--animate-time', oper.time + 'ms'); this.el.style.setProperty('--animate-func', oper.func); this.el.style.setProperty('--animate-delay', oper.delay + 'ms'); this.el.style.setProperty('--animate-direction', oper.direction); this.el.currentAnimationHandler = oper.handler; this.el.addEventListener('animationend', this.handleAnimationEnd); console.log('added listener.'); this.el.classList.add('animate-transform'); } } handleAnimationEnd(event) { console.log('handleAnimationEnd called.'); // Update the from coordinates to be the to coordinates for the next animation this.el.style.setProperty('--translate-from-x', this.el.style.getPropertyValue('--translate-to-x')); this.el.style.setProperty('--translate-from-y', this.el.style.getPropertyValue('--translate-to-y')); event.target.classList.remove('animate-transform'); event.target.removeEventListener('animationend', this.handleAnimationEnd); console.log('removed listener.'); if (this.el.currentAnimationHandler) { this.el.currentAnimationHandler(); } this.move(); // Trigger the next animation in the queue } }
问题根源
- 无过渡跳转:移除
animate-transform类后立即添加该类,浏览器会合并这两次样式变更,不会重新触发动画,导致直接跳转到终点。 - 后续动画失效:
animationend事件仅触发一次,是因为样式合并导致第二次动画根本没真正执行,自然不会触发事件;同时同步调用move没有给浏览器足够时间处理样式更新,导致后续动画的事件监听无法正常工作。
修复后的代码
export class AnimateElement { constructor(el) { this.el = el; this.animationQueue = []; this.handleAnimationEnd = this.handleAnimationEnd.bind(this); } addAnimation(fromx, fromy, tox, toy, delay, time, func = 'linear', direction = 'forwards', handler = null) { this.animationQueue.push({ fromx, fromy, tox, toy, delay, time, func, direction, handler }); } move() { if (this.animationQueue.length === 0) return; const oper = this.animationQueue.shift(); // 获取当前元素的最终位置作为下一个动画的起点 const currentToX = parseFloat(this.el.style.getPropertyValue('--translate-to-x')) || 0; const currentToY = parseFloat(this.el.style.getPropertyValue('--translate-to-y')) || 0; const fromx = oper.fromx !== null ? oper.fromx : currentToX; const fromy = oper.fromy !== null ? oper.fromy : currentToY; // 更新动画变量 this.el.style.setProperty('--translate-from-x', `${fromx}px`); this.el.style.setProperty('--translate-from-y', `${fromy}px`); this.el.style.setProperty('--translate-to-x', `${oper.tox}px`); this.el.style.setProperty('--translate-to-y', `${oper.toy}px`); this.el.style.setProperty('--animate-time', `${oper.time}ms`); this.el.style.setProperty('--animate-func', oper.func); this.el.style.setProperty('--animate-delay', `${oper.delay}ms`); this.el.style.setProperty('--animate-direction', oper.direction); this.el.currentAnimationHandler = oper.handler; // 避免重复绑定事件,先移除再添加 this.el.removeEventListener('animationend', this.handleAnimationEnd); this.el.addEventListener('animationend', this.handleAnimationEnd); // 强制浏览器重绘,确保样式变更被识别 void this.el.offsetWidth; this.el.classList.add('animate-transform'); } handleAnimationEnd(event) { // 移除动画类 event.target.classList.remove('animate-transform'); event.target.removeEventListener('animationend', this.handleAnimationEnd); // 执行当前动画的回调 if (this.el.currentAnimationHandler) { this.el.currentAnimationHandler(); this.el.currentAnimationHandler = null; } // 延迟触发下一个动画,确保样式更新完成 requestAnimationFrame(() => { this.move(); }); } }
修复要点
- 强制重绘:通过
void this.el.offsetWidth触发浏览器重绘,避免样式合并,让动画类的添加能正确触发过渡效果。 - 延迟启动下一个动画:使用
requestAnimationFrame确保当前动画的样式清理完成后再启动下一个动画,保证animationend事件能正常触发。 - 事件监听去重:添加新监听前先移除一次,防止重复绑定导致的逻辑混乱。
内容的提问来源于stack exchange,提问作者omega royal
相关产品推荐
相关产品推荐

