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

如何用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);
    });
}

问题分析与修复方案

核心问题

  1. animationend事件触发多次:你给元素绑定了两个动画(tilt-n-move-shaking和text-pulse),每个动画结束都会触发一次animationend。原代码只监听了一次事件就移除了监听器,第二个动画结束时没有对应的回调,导致Promise永远无法resolve,程序挂起。
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 01:06:08