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
相关产品推荐
相关产品推荐

