如何使用JavaScript在同一个元素上按顺序触发多个动画?

问题描述 投票:0回答:1

我是 JavaScript 新手,正在开发启动应用程序。我正在尝试编写代码,使动画在同一文本元素上执行多次(按顺序),并且文本在动画之间变化。在第一个动画完成之前,不应发生文本更改和第二个动画,依此类推。

使用我编写的代码,文本在单个动画的过程中会更改几次,然后无限期地挂起。我假设这是异步/等待或动画结束侦听器的问题......有更多经验的人(基本上每个人,哈哈)有一个方向可以指出我吗?如果有任何意见,我将不胜感激!

这是带有我的 JS 代码的示例 HTML:

更新:添加了可执行堆栈片段。

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);
    });
}
.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 }
}
<div>
  <h1 class="dialogue" onclick="initAnim()">Click me</h1>
</div>

javascript css css-animations
1个回答
0
投票

这里有两个问题。
第一个,也是最大的一个,是在两次调用

setDialogue
之间,您确实同步删除和添加类。 确实,如果我们解开代码,

dialogueEl.textContent = "First text"; await waitforAnimation(dialogueEl); // Sync after this line dialogueEl.classList.remove("dialogueEnter"); // <- end first call to setDialogue() dialogueEl.textContent = "Second text"; // <- begin second call to setDialogue() dialogueEl.classList.add("dialogueEnter");

对于 CSSOM,classList 从未改变,因为它只会在下一次回流期间重新计算,因此它不会看到新的动画应该开始。您可以参考
我的这个答案

以获取有关此行为的更多详细信息。一个快速解决方法是使用其中一个触发器来触发回流。 第二个问题是您为每个元素设置两个具有不同持续时间的动画。您捕获的

animationend

事件是较短的

tilt-n-move-shaking
之一。要仅等待较长的一个,您可以检查收到的动画事件的
animationName
属性:

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.offsetWidth; // force a reflow dialogueEl.classList.add("dialogueEnter"); await waitforAnimation(dialogueEl); dialogueEl.classList.remove("dialogueEnter"); } function waitforAnimation(element) { return new Promise(resolve => { function handleAnimationEnd(evt) { if (evt.animationName === "text-pulse") { element.removeEventListener('animationend', handleAnimationEnd); resolve(); } else { console.log("ignoring ", evt.animationName); } } element.addEventListener('animationend', handleAnimationEnd); }); }
.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); } /* <- There was a typo here */
    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 }
}
<div>
  <h1 class="dialogue" onclick="initAnim()">Click me</h1>
</div>

但是,所有这些都可以通过

Web Animation API

得到极大简化。 在那里,您可以直接点击 CSSOM 将生成的动画对象,而无需经历 DOM -> CSSOM -> 渲染器的漫长而缓慢的路径。您无需担心下一次回流何时发生,无需使页面中所有元素的所有框失效,并且所有内容都集中在一个位置,使维护变得更容易。

const shakingKeyframes = [ { transform: "translate(0, 0) rotate(0deg)" }, { transform: "translate(10px, 10px) rotate(10deg)" }, { transform: "translate(0, 0) rotate(0deg)" }, { transform: "translate(-10px, 10px) rotate(-10deg)" }, { transform: "translate(0, 0) rotate(0deg)" }, ]; const shakingOptions = { duration: 250, fill: "forwards" }; const pulseKeyframes = [ { fontSize: "1em" }, { fontSize: "2em" }, { fontSize: "1.6em" }, ]; const pulseOptions = { duration: 600, fill: "forwards" }; 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; const shakingAnim = dialogueEl.animate(shakingKeyframes, shakingOptions); const pulseAnim = dialogueEl.animate(pulseKeyframes, pulseOptions); await Promise.all([shakingAnim.finished, pulseAnim.finished]); }
<div>
  <h1 class="dialogue" onclick="initAnim()">Click me</h1>
</div>

© www.soinside.com 2019 - 2024. All rights reserved.