Canceling stop the animation made with requestAnimationFrame()

35 Views Asked by At

Im making a timer progress bar animation in react. Im doing this using requestAnimationFrame(). The issue I have is that I cannot stop that animation with cancelAnimationFrame() despite setting an id to the animation and then reffering to that id while cancelling (handleStop func). Why?

Edit: Hmm, maybe it stops requestAnimationFrame() but it won't stop tranform animation that was already set?

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"));
.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;
}
<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

There are 1 best solutions below

2
Seojin Kim On BEST ANSWER

Maybe you can try these steps.

  1. Do not call requestAnimationFrame in setState() callback function.
  2. Try use useRef() instead of useState() to manage timer id.

I wrote the code with those steps and it worked well in this code sandbox.

  const timerRef = useRef(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);
          return remainingTime;
        }
        return 0;
      });
      timerRef.current = requestAnimationFrame(animate);
    };

    timerRef.current = requestAnimationFrame(animate);

    return () => {
      if (timerRef.current !== null) {
        cancelAnimationFrame(timerRef.current);
        timerRef.current = null;
      }
    };
  }, []);