进度条动画暂停后回退错位,求代码问题排查
进度条暂停后回退问题排查与修复
问题原因
你当前的实现同时依赖每秒更新的React状态timeLeft和连续运行的CSS动画控制进度条位置,两者无法做到完全同步:
setInterval每秒更新一次timeLeft,是离散的整秒更新;- CSS动画是连续平滑的,在两次
timeLeft更新之间,动画已经推进了部分进度。
点击暂停时,移除animated类后,进度条会立刻切换到timeLeft/totalTime对应的离散位置,这个位置比CSS动画实际运行到的位置更靠后,视觉上就出现了回退。
修复方案
暂停时把CSS动画的实时进度同步到timeLeft,让transform的位置和动画暂停时的位置完全匹配,同时保留原有的重启逻辑:
修改后的JavaScript代码
const { useState, useEffect, useRef } = React; const App = () => { const totalTime = 10; const [timeLeft, setTimeLeft] = useState(totalTime); const [isTimerAnimated, setIsTimerAnimated] = useState(true); const progressRef = useRef(null); // 获取进度条DOM元素 useEffect(() => { const interval = setInterval(() => { if (timeLeft > 0 && isTimerAnimated) { setTimeLeft((prevTimeLeft) => prevTimeLeft - 1); } }, 1000); return () => clearInterval(interval); }, [timeLeft, isTimerAnimated]); const handleAnimationState = () => { if (isTimerAnimated) { // 暂停时同步动画实时进度到timeLeft if (progressRef.current) { const animation = progressRef.current.getAnimations()[0]; if (animation) { const elapsedTime = animation.currentTime; const remainingTime = totalTime - elapsedTime; setTimeLeft(Math.max(0, Math.round(remainingTime * 10) / 10)); // 保留一位小数保证精度 } } setIsTimerAnimated(false); } else { setIsTimerAnimated(true); setTimeLeft(totalTime); } }; return ( <div> <div className="progress-bar"> <div ref={progressRef} className={`${isTimerAnimated ? "animated" : ""}`} style={{ transform: `scaleX(${timeLeft ? timeLeft / totalTime : 0})`, animationDuration: `${totalTime}s`, }} /> </div> <button onClick={handleAnimationState}> {isTimerAnimated ? "Pause the animation" : "Restart the animation"} </button> </div> ); }; ReactDOM.render(<App />, document.getElementById("root"));
CSS代码(保持不变)
@keyframes timerAnimation { from { transform: scaleX(1); } to { transform: scaleX(0); } } .progress-bar { height: 10px; width: 300px; background-color: #fdb913; border-radius: 10px; overflow: hidden; margin-top: 2rem; margin-bottom: 3rem; } .progress-bar div { background-color: grey; height: 100%; animation-play-state: paused; animation-fill-mode: forwards; transform-origin: left center; } .progress-bar div.animated { animation: timerAnimation linear; animation-play-state: running; }
HTML代码(保持不变)
<div id="root"> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.production.min.js"></script> </div>
修复说明
- 新增
progressRef获取进度条元素,暂停时通过getAnimations()拿到动画实例,读取当前已运行的精确时间; - 计算剩余时间并更新
timeLeft,确保transform对应的位置和动画暂停时的实时进度完全一致; - 原有的重启逻辑完全保留,点击重启按钮仍会重置进度条到初始状态并重新启动动画。
内容的提问来源于stack exchange,提问作者Adrian
相关产品推荐
相关产品推荐

