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

JavaScript Web Animations反向播放时delay属性失效问题咨询

Web Animations API反向播放忽略delay:是预期行为,这样解决

这是预期行为

反向播放时动画的delay属性不会自动反向调整,这是API的设计逻辑。delay是正向启动动画前的等待时间,反向播放时,动画会直接从结束状态往回走,不会把原有的正向延迟转换成反向的“延迟启动”,所以所有动画会同步执行,导致字符一起出现。

为什么会这样?

举个例子:第6个字符的正向动画延迟了5*250=1250ms才开始淡出,当你反向播放时,这个动画会立刻从“透明”状态开始往“不透明”走,不会等待1250ms再启动——因为delay只作用于正向的启动时机,反向播放没有“反向延迟”的逻辑。

解决办法:手动实现交错反向效果

要让最后消失的字符最先重新显示,需要手动为反向播放计算每个动画的延迟,或者预先创建反向动画栈。

方案1:动态调整反向播放的延迟

不需要重新创建动画,在反向时根据原延迟计算反向延迟(总延迟时长减去当前动画的原延迟),让原最后启动的动画最先开始反向:

buttonReverse.addEventListener("click", () => {
  const totalDelay = (characters.length - 1) * 250; // 计算所有动画的总延迟时长
  animationStack.forEach((animation, index) => {
    // 把动画定位到结束状态,确保反向从正确位置开始
    animation.currentTime = animation.effect.getTiming().duration;
    // 设置反向延迟:总延迟 - 当前动画的原正向延迟
    animation.effect.updateTiming({ delay: totalDelay - index * 250 });
    animation.reverse();
  });
});

同时要修改Start按钮,重置动画的延迟为正向值:

buttonStart.addEventListener("click", () => {
  animationStack.forEach((animation, index) => {
    animation.effect.updateTiming({ delay: index * 250 });
    animation.currentTime = 0;
    animation.playbackRate = 1;
    animation.play();
  });
});

方案2:预先创建正向/反向两个动画栈

初始化时同时生成淡出(正向)和淡入(反向)的动画栈,反向动画的delay按逆序设置:

// 正向淡出动画栈
const fadeOutStack = [];
// 反向淡入动画栈
const fadeInStack = [];
const totalChars = characters.length;

characters.forEach((character, index) => {
  // 正向淡出:延迟index*250ms
  const fadeOutDef = structuredClone(animationDefinitionDefault);
  fadeOutDef.timing.delay = index * 250;
  fadeOutStack[index] = character.animate(...Object.values(fadeOutDef));
  fadeOutStack[index].pause();

  // 反向淡入:延迟(totalChars-1 - index)*250ms,keyframes反过来
  const fadeInDef = structuredClone(animationDefinitionDefault);
  fadeInDef.keyframes = [{ opacity: "0.1" }, { opacity: "1" }];
  fadeInDef.timing.delay = (totalChars - 1 - index) * 250;
  fadeInStack[index] = character.animate(...Object.values(fadeInDef));
  fadeInStack[index].pause();
});

// Start按钮播放正向栈
buttonStart.addEventListener("click", () => {
  fadeInStack.forEach(anim => anim.pause());
  fadeOutStack.forEach(anim => {
    anim.currentTime = 0;
    anim.play();
  });
});

// Reverse按钮播放反向栈
buttonReverse.addEventListener("click", () => {
  fadeOutStack.forEach(anim => anim.pause());
  fadeInStack.forEach(anim => {
    anim.currentTime = 0;
    anim.play();
  });
});

完整可运行代码(方案1)

HTML

<div id="animation-container">
  <span>1</span>
  <span>2</span>
  <span>3</span>
  <span>4</span>
  <span>5</span>
  <span>6</span>
</div>

<button type="button" id="button-start">Start</button>
<button type="button" id="button-reverse">Reverse(修复版)</button>
<button type="button" id="button-playbackRate">playbackRate = -1</button>
<button type="button" id="button-updatePlaybackRate">updatePlaybackRate(-1)</button>

CSS

#animation-container {
  font-size: 20px;
  margin-bottom: 1em;
}

button {
  font-size: 16px;
  margin-right: 8px;
}

JavaScript

const animationContainer = document.querySelector("#animation-container");
const buttonStart = document.querySelector("#button-start");
const buttonReverse = document.querySelector("#button-reverse");
const buttonPlaybackRate = document.querySelector("#button-playbackRate");
const buttonUpdatePlaybackRate = document.querySelector("#button-updatePlaybackRate");

const animationDefinitionDefault = {
  keyframes: [
    { opacity: "1" },
    { opacity: "0.1" },
  ],

  timing: {
    duration: 500,
    delay: 0,
    iterations: 1,
    easing: "linear",
    fill: "both",
  }
};

const animationStack = [];
const characters = animationContainer.querySelectorAll("span");
const totalDelay = (characters.length - 1) * 250;

// 创建正向动画
characters.forEach((character, index) => {
  const delay = index * 250;
  const animationDefinition = structuredClone(animationDefinitionDefault);
  animationDefinition.timing.delay = delay;
  animationStack[index] = character.animate(...Object.values(animationDefinition));
  animationStack[index].pause();
});

// 开始按钮
buttonStart.addEventListener("click", () => {
  animationStack.forEach((animation, index) => {
    animation.effect.updateTiming({ delay: index * 250 });
    animation.currentTime = 0;
    animation.playbackRate = 1;
    animation.play();
  });
});

// 修复后的反向按钮
buttonReverse.addEventListener("click", () => {
  animationStack.forEach((animation, index) => {
    animation.currentTime = animation.effect.getTiming().duration;
    animation.effect.updateTiming({ delay: totalDelay - index * 250 });
    animation.reverse();
  });
});

// 原逻辑按钮(无交错效果)
buttonPlaybackRate.addEventListener("click", () => {
  animationStack.forEach(animation => {
    animation.playbackRate = -1;
    animation.play();
  });
});

buttonUpdatePlaybackRate.addEventListener("click", () => {
  animationStack.forEach(animation => {
    animation.updatePlaybackRate(-1);
    animation.ready.then(() => {
      animation.play();
    });
  });
});

内容的提问来源于stack exchange,提问作者sp00n

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 15:47:15