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

React中requestAnimationFrame进度条动画无法被cancelAnimationFrame终止的原因

问题:React中requestAnimationFrame动画无法用cancelAnimationFrame停止

我在React中使用requestAnimationFrame实现了一个计时器进度条动画,但尽管为动画设置了ID,并在handleStop函数中引用该ID调用cancelAnimationFrame,却无法停止动画。这是为什么?

编辑补充:嗯,会不会是cancelAnimationFrame确实停止了requestAnimationFrame,但无法终止已经设置好的transform动画?


原代码

JavaScript

const { useState, useEffect } = React;

const App = () => {
  const totalTime = 10000;
  const [timeLeft, setTimeLeft] = useState(null);
  const [timerId, setTimerId] = useState(null);

  useEffect(() => {
      setTimeLeft(totalTime);

      const start = performance.now();
      const animate = (time) => {
        const elapsedTime = time - start;
        setTimeLeft((prevTimeLeft) => {
          if (prevTimeLeft !== null && prevTimeLeft > 0) {
            const remainingTime = Math.max(totalTime - elapsedTime, 0);
            requestAnimationFrame(animate);
            return remainingTime;
          }
          return 0;
        });
      };

    const id = requestAnimationFrame(animate);
    setTimerId(id);

    return () => {
      cancelAnimationFrame(id);
    };
  }, []);

  const handleStop = () => {
    if (timerId) {
      cancelAnimationFrame(timerId);
      setTimerId(null);
    }
  };

  return (
    <div>
      <div className="progress-bar">
        <div
          style={{
            transform: `scaleX(${timeLeft ? timeLeft / totalTime : 0})`,
          }}
        />
      </div>
      <button onClick={handleStop}>Pause the animation</button>
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));

CSS

.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%;
  transform-origin: left center;
}

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>

问题原因

  1. 动画ID未跟踪最新值:你只保存了第一次调用requestAnimationFrame返回的ID,但在animate函数内部,每次执行都会再次调用requestAnimationFrame(animate),生成新的动画ID,这些新ID从未被保存到timerId状态中。调用cancelAnimationFrame(timerId)只能终止第一次的动画帧,后续的动画帧仍在持续执行,导致动画停不下来。

  2. 关于你补充的疑问:这里的transform是通过React状态实时更新的内联样式,不是CSS关键帧动画,不存在“已设置的transform动画无法终止”的问题,动画继续运行的根源还是requestAnimationFrame的ID没跟踪到位。


修复方案

使用useRef替代useState来保存动画ID,因为useRef的更新不会触发组件重渲染,且能实时跟踪最新的动画ID:

修改后的JavaScript代码

const { useState, useEffect, useRef } = React;

const App = () => {
  const totalTime = 10000;
  const [timeLeft, setTimeLeft] = useState(null);
  const animationIdRef = useRef(null); // 用ref保存最新动画ID

  useEffect(() => {
    setTimeLeft(totalTime);
    const start = performance.now();

    const animate = (time) => {
      const elapsedTime = time - start;
      setTimeLeft((prevTimeLeft) => {
        if (prevTimeLeft !== null && prevTimeLeft > 0) {
          const remainingTime = Math.max(totalTime - elapsedTime, 0);
          // 保存新的动画ID到ref
          animationIdRef.current = requestAnimationFrame(animate);
          return remainingTime;
        }
        return 0;
      });
    };

    // 初始化动画并保存ID
    animationIdRef.current = requestAnimationFrame(animate);

    return () => {
      if (animationIdRef.current) {
        cancelAnimationFrame(animationIdRef.current);
      }
    };
  }, []);

  const handleStop = () => {
    if (animationIdRef.current) {
      cancelAnimationFrame(animationIdRef.current);
      animationIdRef.current = null; // 清空ID
    }
  };

  return (
    <div>
      <div className="progress-bar">
        <div
          style={{
            transform: `scaleX(${timeLeft ? timeLeft / totalTime : 0})`,
          }}
        />
      </div>
      <button onClick={handleStop}>Pause the animation</button>
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 08:06:06