如何用JavaScript实现同一元素上多段动画依次触发?
问题描述
我是JavaScript新手,正在开发一款创业应用。我希望编写代码实现同一文本元素依次执行多次动画,且动画之间切换文本,必须等前一个动画完成后再进行文本切换和下一段动画。但我编写的代码存在问题:一次动画过程中文本多次切换,之后程序无限挂起。我怀疑是async/await或animationend监听器的问题,恳请有经验的开发者指点方向!
UPDATE: 添加了可运行的代码片段。
原代码片段
HTML
<div> <h1 class="dialogue" onclick="initAnim()">Click me</h1> </div>
CSS
.dialogueEnter { animation: tilt-n-move-shaking 0.25s forwards, text-pulse 0.6s forwards; } @keyframes tilt-n-move-shaking { 0% { transform: translate(0, 0) rotate(0deg); } 25% { transform: translate(10px, 10px) rotate(10deg); } 50% { transform: translate(0, 0) rotate(0eg); } 75% { transform: translate(-10px, 10px) rotate(-10deg); } 100% { transform: translate(0, 0) rotate(0deg); } } @keyframes text-pulse { 0% { font-size: 1em } 75% { font-size: 2em } 100% { font-size: 1.6em } }
JavaScript
async function initAnim() { await setDialogue("First text"); await setDialogue("Second text"); await setDialogue("Third text"); await setDialogue("Fourth text"); await setDialogue("Fifth text"); } async function setDialogue(text) { const dialogueEl = document.querySelector(".dialogue"); dialogueEl.textContent = text; dialogueEl.classList.add("dialogueEnter"); await waitforAnimation(dialogueEl); dialogueEl.classList.remove("dialogueEnter"); } function waitforAnimation(element) { return new Promise(resolve => { function handleAnimationEnd() { element.removeEventListener('animationend', handleAnimationEnd); resolve(); } element.addEventListener('animationend', handleAnimationEnd); }); }
问题分析与修复方案
核心问题
animationend事件触发多次:你给元素绑定了两个动画(tilt-n-move-shaking和text-pulse),每个动画结束都会触发一次animationend。原代码只监听了一次事件就移除了监听器,第二个动画结束时没有对应的回调,导致Promise永远无法resolve,程序挂起。- CSS笔误:
rotate(0eg)应为rotate(0deg),这个错误会导致动画异常。
修复后的完整代码
HTML
<div> <h1 class="dialogue" onclick="initAnim()">Click me</h1> </div>
CSS
.dialogueEnter { animation: tilt-n-move-shaking 0.25s forwards, text-pulse 0.6s forwards; } @keyframes tilt-n-move-shaking { 0% { transform: translate(0, 0) rotate(0deg); } 25% { transform: translate(10px, 10px) rotate(10deg); } 50% { transform: translate(0, 0) rotate(0deg); } 75% { transform: translate(-10px, 10px) rotate(-10deg); } 100% { transform: translate(0, 0) rotate(0deg); } } @keyframes text-pulse { 0% { font-size: 1em } 75% { font-size: 2em } 100% { font-size: 1.6em } }
JavaScript
async function initAnim() { await setDialogue("First text"); await setDialogue("Second text"); await setDialogue("Third text"); await setDialogue("Fourth text"); await setDialogue("Fifth text"); } async function setDialogue(text) { const dialogueEl = document.querySelector(".dialogue"); // 重置动画状态,避免连续操作时动画不触发 dialogueEl.classList.remove("dialogueEnter"); // 强制浏览器重绘,确保类移除生效 void dialogueEl.offsetWidth; dialogueEl.textContent = text; dialogueEl.classList.add("dialogueEnter"); // 传入要等待的动画数量(这里是2个) await waitforAnimation(dialogueEl, 2); dialogueEl.classList.remove("dialogueEnter"); } function waitforAnimation(element, totalAnimations) { return new Promise(resolve => { let completedCount = 0; function handleAnimationEnd() { completedCount++; // 所有动画完成后才resolve并移除监听 if (completedCount === totalAnimations) { element.removeEventListener('animationend', handleAnimationEnd); resolve(); } } element.addEventListener('animationend', handleAnimationEnd); }); }
优化说明
- 新增
totalAnimations参数,确保所有绑定的动画都完成后才继续执行下一步 - 添加
void dialogueEl.offsetWidth强制重绘,解决连续添加/移除动画类时浏览器优化导致的动画不触发问题 - 修正了CSS中的笔误,保证动画正常运行
内容的提问来源于stack exchange,提问作者ewait
相关产品推荐
相关产品推荐

