如何快速加速CSS沙漏动画至初始状态后重启?
问题描述
我制作了一个带有CSS关键帧动画的沙漏元素,动画默认无限循环,每次迭代结束后旋转回初始位置继续运行。现在需要实现点击按钮时,不立即重启动画,而是快速加速至初始位置,之后恢复正常动画速度继续循环。
我尝试点击时修改animation-duration和animation-iteration-count属性,但动画会立即停止;且在animationend事件中恢复无限循环设置时,动画会从最后位置开始,达不到预期效果。
解决方案
核心思路是通过类切换控制动画状态,避免直接修改内联样式导致的动画中断,同时保证快速复位后能无缝衔接正常循环。
实现要点
- 拆分动画状态为两个CSS类:
.normal-loop:保留原动画的无限循环和正常时长.fast-reset:设置短时长和单次迭代,用于快速完成剩余动画回到初始位置
- 点击按钮时,切换元素的动画类,触发快速复位流程
- 监听
animationend事件,复位完成后切回正常循环类,恢复无限动画
完整代码
HTML
<div class="hourglass normal-loop"> <div class="before normal-loop"></div> <div class="after normal-loop"></div> </div> <button>快速复位</button>
CSS
* { border: 0; box-sizing: border-box; margin: 0; padding: 0; } :root { --bg:#e3e4e8; --fg:#2e3138; --primary:#255ff4; font-size: calc(16px + (24 - 16) *(100vw - 320px) / (1280 - 320)); } body { display: grid; font:1em/1.5 sans-serif; height: 100vh; place-items:center; } /* 正常循环动画类 */ .normal-loop { animation-duration: 4s; animation-iteration-count: infinite; } /* 快速复位动画类 */ .fast-reset { animation-duration: 0.5s; animation-iteration-count: 1; animation-timing-function: linear; } .hourglass { --polygonH: polygon(0% 0%,100% 0%,100% 5.55%,95% 5.55%,95% 28%,60% 46%,60% 54%,95% 72%,95% 94.45%,100% 94.45%,100% 100%,0% 100%,0% 94.45%,5% 94.45%,5% 72%,40% 54%,40% 46%,5% 28%,5% 5.55%,0% 5.55%); animation-name: flip; animation-timing-function: ease-in-out; background-image: linear-gradient(var(--primary) 0.5em, #737a8c55 0.5em 8.5em, var(--primary) 8.5em); clip-path: var(--polygonH); -webkit-clip-path: var(--polygonH); overflow: hidden; position: relative; width: 5em; height: 9em; z-index: 0; } .hourglass .before, .hourglass .after { animation-timing-function: linear; content: ""; display: block; position: absolute; } .hourglass .before { --polygonB1: polygon(0% 0%,100% 0%,100% 24%,50% 47%,50% 47%,50% 47%,50% 47%,50% 47%,50% 47%,50% 47%,50% 47%,0% 24%); --polygonB2: polygon(0% 4%,100% 4%,100% 24%,55% 45%,55% 100%,55% 100%,55% 100%,45% 100%,45% 100%,45% 100%,45% 45%,0% 24%); --polygonB3: polygon(0% 24%,100% 24%,100% 24%,55% 45%,55% 80%,100% 100%,100% 100%,0% 100%,0% 100%,45% 80%,45% 45%,0% 24%); --polygonB4: polygon(45% 45%,55% 45%,55% 45%,55% 45%,55% 58%,100% 76%,100% 100%,0% 100%,0% 76%,45% 58%,45% 45%,45% 45%); --polygonB5: polygon(50% 53%,50% 53%,50% 53%,50% 53%,50% 53%,100% 76%,100% 100%,0% 100%,0% 76%,50% 53%,50% 53%,50% 53%); animation-name: fill; background: var(--fg); background-size: 100% 3.6em; clip-path: var(--polygonB1); top: 0.5em; left: 0.5em; width: 4em; height: 8em; z-index: 1; } .hourglass .after { animation-name: glare; background: linear-gradient(90deg,#0000 0.5em,#0003 0.5em 1.5em,#0000 1.5em 3.5em,#fff3 3.5em 4.5em,#fff0 4.5em 6.5em,#0003 6.5em 7.5em,#0000 7.5em) 0 0 / 100% 0.5em, linear-gradient(90deg,#0000 0.75em,#0003 0.75em 1.25em,#0000 1.25em 3.75em,#fff3 3.75em 4.25em,#fff0 4.25em 6.75em,#0003 6.75em 7.25em,#0000 7.25em) 0 0.5em / 100% 8em, linear-gradient(90deg,#0000 0.5em,#0003 0.5em 1.5em,#0000 1.5em 3.5em,#fff3 3.5em 4.5em,#fff0 4.5em 6.5em,#0003 6.5em 7.5em,#0000 7.5em) 0 100% / 100% 0.5em; background-repeat: repeat-x; top: 0; left: -3em; width: 200%; height: 100%; z-index: 2; } @keyframes fill{ from{ clip-path: var(--polygonB1); -webkit-clip-path:var(--polygonB1); } 10%{ clip-path: var(--polygonB2); -webkit-clip-path:var(--polygonB2); } 45%{ clip-path: var(--polygonB3); -webkit-clip-path:var(--polygonB3); } 80%{ clip-path: var(--polygonB4); -webkit-clip-path:var(--polygonB4); } 85%, to{ clip-path: var(--polygonB5); -webkit-clip-path:var(--polygonB5); } } @keyframes glare{ from, 90% { transform: translateX(0); } to { transform: translateX(3em); } } @keyframes flip{ from, 90% { transform: rotate(0); } to{ transform: rotate(180deg); } } button{ padding: 5px 15px; border: 1px solid gray; margin-top: 2em; }
JavaScript
const hourglass = document.querySelector(".hourglass"); const before = document.querySelector(".before"); const after = document.querySelector(".after"); const button = document.querySelector('button'); const resetElements = [hourglass, before, after]; const handleClick = () => { // 切换到快速复位动画 resetElements.forEach(el => { el.classList.remove('normal-loop'); el.classList.add('fast-reset'); // 强制重绘,确保动画触发 void el.offsetWidth; }); }; const handleAnimationEnd = (event) => { // 仅处理快速复位动画的结束事件 if (event.target.classList.contains('fast-reset')) { resetElements.forEach(el => { el.classList.remove('fast-reset'); el.classList.add('normal-loop'); // 强制重绘,恢复正常循环 void el.offsetWidth; }); } }; button.addEventListener('click', handleClick); // 给所有动画元素绑定结束事件 resetElements.forEach(el => { el.addEventListener('animationend', handleAnimationEnd); });
内容的提问来源于stack exchange,提问作者Boombaar
相关产品推荐
相关产品推荐

