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

进度条动画暂停后回退错位,求代码问题排查

进度条暂停后回退问题排查与修复

问题原因

你当前的实现同时依赖每秒更新的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 20:04:51