Using RequestAnimationFrame In React For Smoothest Animations
Published: 2024-06-15 14:15:36


by [Chukwuemeka Timothy Ofili](https://blog.openreplay.com/authors/chukwuemeka-timothy-ofili)



<blockquote><em>
This article will discuss the `requestAnimationFrame` method, delving into why you should use it and ways to leverage it in performing animation in `React`, and showing how to use it to create smooth progress loaders.
</em></blockquote>




<div style="background-color:#efefef; border-radius:8px; padding:10px; display:block;">
<hr/>
<h3><em>Session Replay for Developers</em></h3> 
<p><em>Uncover frustrations, understand bugs and fix slowdowns like never before with <strong><a href="https://github.com/openreplay/openreplay" target="_blank">OpenReplay</a></strong> — an open-source session replay suite for developers. It can be <strong>self-hosted</strong> in minutes, giving you complete control over your customer data.</em></p>
<img alt="OpenReplay" style="margin-top:5px; margin-bottom:5px;" width="768" height="400" src="https://raw.githubusercontent.com/openreplay/openreplay/main/static/openreplay-git-hero.svg" class="astro-UXNKDZ4E" loading="lazy" decoding="async">
<p><em>Happy debugging! <a href="https://openreplay.com" target="_blank">Try using OpenReplay today.</a></em><p>
<hr/>
</div>




Animation brings life to a digital application. Modern web applications leverage animations and transitions to create an engaging user experience. Proper application of animations can build a strong connection between the users and the content of a web application. In the past, developers relied on [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout) and [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval) to perform JavaScript animations. These traditional methods invoke a function 60 times per second to imitate a smooth animation. Their inconsistencies and unnecessary repaint prompted a more performant and reliable method. [Paul Irish](https://www.paulirish.com/2011/requestanimationframe-for-smart-animating/) introduced the [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) (rAF) in 2011 as a better approach for web animation. This method allows the browser to decide the repaint rate, optimizing web animations.


## What is `requestAnimationFrame`?
The `requestAnimationFrame` is a method provided by browsers for animations in JavaScript. It requests the browser to invoke a specified function and update an animation. The `requestAnimationFrame` receives a single argument, a `callback` function, called at intervals. The frequency of  `callback` function calls matches a display refresh rate of 60Hz. This frequency is equal to 60 cycles/frames per second. Its syntax is as follows:
```javascript
requestAnimationFrame(callback)
```
Invoking the `requestAnimationFrame` schedules a single call to the `callback` function. Animating another frame involves repeatedly calling the `requestAnimationFrame` method. Nevertheless, understanding its role in efficient animation is essential.
### What is the Role of `requestAnimationFrame` in Efficient Animation?
The `requestAnimationFrame` method plays a vital role in efficient animation. It synchronizes animations with the browser's rendering cycle. The higher the frame rate, the smoother the animation. But, a higher frame rate requires more resources, impacting performance.

Unlike the conventional approach, the `requestAnimationFrame` can achieve optimal frame rate by:
* Enabling the browser to decide the next repaint cycle and optimize it.
* Limiting animations to a 60Hz display refresh rate, ensuring smooth and efficient animations.
* Reducing unnecessary central processing unit (CPU) usage and conserves resources.

The following section will discuss stopping an animation request.
### Canceling the `requestionAnimationFrame`
JavaScript animation with `requestAnimationFrame` involves recursively calling a function to execute a particular code block. Allowing further calls to the `callback `by `requestAnimationFrame` after completing an animation can lead to memory leaks and affect performance. Fortunately, JavaScript provides the `cancelAnimationFrame` method to stop further requests to the browser to refresh the `callback` function after a completed animation. Consider the syntax below:
```javascript
function callback() {
  // animation code block
}

const requestId = requestAnimationFrame(callback);
cancelAnimationFrame(requestId);
```
When invoked, `requestAnimationFrame` returns a long integer value uniquely identifying the entry in the `callback` list. This value gets assigned to the `requestId` variable. Passing the `requestId` to the `cancelAnimationFrame` method cancels the refresh `callback` request.
## Why `requestAnimationFrame`?
The `requestAnimationFrame` provides an efficient way to schedule animation in web browsers. The `requestAnimationFrame` offers several benefits, including:
* It allows the browser to update animation states precisely between frames. This consistency results in smooth, higher-fidelity animations.
* It repaints less often in hidden or inactive tabs and during CPU overload. This phenomenon helps conserve system resources, leading to much longer battery life.
* It leverages hardware acceleration in modern browsers to optimize animations. This approach enhances performance and reduces CPU load in devices with limited resources. 



## Utilizing `requestAnimationFrame` in `React`
Performing animations with `requestAnimationFrame` in `React` introduces intricacies distinct from Vanilla JavaScript. It involves a good understanding of `state`, `props`, and [`React's` life-cycle methods](https://react.dev/learn/lifecycle-of-reactive-effects). Efficient side effects management is essential for consistent performance and behavior. Let's see how to achieve these in the later sub-sections.
### Managing Side-effects with Hooks
`React` provides several hooks to manage side effects within a `React` component. For this implementation, we will use the essential hooks. Consider the following approach to create a progress counter that stops at 100:
```jsx
import { useEffect, useRef, useState } from "react";

export default function App() {
  const [value, setValue] = useState(0);
  const runAgain = useRef(performance.now() + 100);
  const progressTimer = useRef(performance.now() + 100);

  const requestIdRef = useRef(0);

  function nextFrame(timestamp) {
    if (runAgain.current <= timestamp) {
      // Run the animation

      if (progressTimer.current <= timestamp) {
        // When the native timestamp catches up to the set interval 

       //animate the element with the current value
        const nextValue = 1;

        setValue((preValue) =>
          preValue + nextValue < 100 ? (preValue += nextValue) : 100,
        );
        
        progressTimer.current =
          timestamp + 100;
      }

      runAgain.current = timestamp + 100;
    }
    // batch the animation for the next frame paint
    requestIdRef.current = requestAnimationFrame(nextFrame);
  }

  useEffect(function () {
    requestIdRef.current = requestAnimationFrame(nextFrame);

    return () => cancelAnimationFrame(requestIdRef.current);
  }, []);

  return (
    <div className="App">
      <p>{value}</p>
    </div>
  );
}
```
Let's explain what is happening here:
* We added a way to keep track of the counter value using the `useState` hook with the `value` and `setValue` variables.
* We also utilized the `useRef` hook to declare three other variables. This ensures they remain unaffected and don't trigger any side effects when updated.
* We initialized the `runAgain` and `progressTimer` `ref` variables with the `performance.now()` method. This initialization captures the current `timestamp` when the page first loaded. It also synchronizes their values with the `timestamp` from the `requestAnimationFrame` `callback` argument.
* The `requestIdRef` variable stores the value the `requestAnimationFrame` method returns.
* We defined the `nextFrame` function as the `callback` for the `requestAnimationFrame` method. This function wraps the counter logic, and the `requestAnimationFrame` recursively invokes it.
* We ensured the conditional code block for `runAgain.current` and `progressTimer.current` executes. This event occurs when they are less than or equal to the current `timestamp` value.
* We updated `runAgain.current` and `progressTimer.current` below the conditional statement code blocks. These updates ensure a smooth repaint after a designated interval.
* Inside a `useEffect` `callback`, we invoked the `requestAnimationFrame` with the `nextFrame` function. We also provided an empty dependency array to the `useEffect` to ensure it runs once.
* Finally, we cleaned up after the initial invocation. In the returned function, ensure to pass `requestIdRef.current` to the `cancelAnimationFrame` method.

But there is a slight bug with this code. Currently, the timed loop doesn't stop once it starts. While this behavior is acceptable in specific scenarios, it defeats our goal. To address this, consider the following: 
```jsx
useEffect(
  function () {
    if (value >= 100) {
      cancelAnimationFrame(requestIdRef.current);
    }
  },
  [value],
);
```
The code snippet above shows a `useEffect` keeping track of the progress value. Placing this code below the previous `useEffect` cancels the animation request once completed.
### Extracting the `requestAnimationFrame` Logic into a Custom Hook
The preceding section shows a progress counter implementation with the `requestAnimationFrame` method. Unfortunately, this approach becomes tedious when duplicating the same logic across several components. So, moving the whole logic to a custom hook becomes necessary and helpful. Let us create a reusable hook to encapsulate our progress counter logic:
```jsx
// useProgressLoader.jsx
function useProgressLoader(interval = 2000) {
  // Values to update
  const [value, setValue] = useState(0);
  const runAgain = useRef(performance.now() + 100);
  const progressTimer = useRef(performance.now() + 100);

  const requestIdRef = useRef(0);

  function nextFrame(timestamp) {
    if (runAgain.current <= timestamp) {
      // Run the animation

      if (progressTimer.current <= timestamp) {
        // When the native timestamp catches up to the set interval 
        // animate the element with the current value
        const nextValue = Math.floor(Math.random() * 25) + 1;
        setValue((prevValue) =>
          (prevValue + nextValue < 100 ? (prevValue += nextValue) : 100)
        );
        // Change to a random interval
        progressTimer.current =
          timestamp + Math.floor(Math.random() * (interval - 800)) + 500;
      }

      runAgain.current = timestamp + 100;
    }
    // batch the animation for the next frame paint
    requestIdRef.current = requestAnimationFrame(nextFrame);
  }

  useEffect(function () {
    requestIdRef.current = requestAnimationFrame(nextFrame);

    return () => cancelAnimationFrame(requestIdRef.current);
  }, []);

  useEffect(() => {
    if (value >= 100) {
      // Stop the animation
      cancelAnimationFrame(requestIdRef.current);
    }
  }, [value]);

  return { value };
}
```
The progress logic is almost identical to the previous implementation, with minor changes:
* We made the interval used to update the `progressTimer.current` value dynamic.
* The `progressTimer.current` gets updated with the current `timestamp` and a random interval.
* We ensured the progress increased with random values from 1 to 25.

Based on these improvements, let's build an animated progress loader.

<CTA_Middle_Frameworks />



## Creating Progress Loader Animations
So far, we demonstrated a basic progress counter with our `requestAnimationFrame` setup. Now, let's develop more advanced loader animations, such as:
* A progress bar
* A circular progress indicator
### Implementing a Progress Bar
A circular progress indicator provides visual feedback about an ongoing task. It moves in a circular motion toward its starting position. Let's show this with the code example below:
```jsx
// ProgressBar.jsx
import "./progressbar.css";

const ProgressBar = () => {
  const value = 50;
  return (
    <section className="">
      <div
        id="progress"
        role="progressbar"
        aria-valuenow={value}
        aria-valuemax={100}
      >
        <div
          style={{ width: value + "%" }}
          id="indicator"
          aria-label="Progress indicator"
        ></div>
        <p>{value}%</p>
      </div>
    </section>
  );
};

export default ProgressBar;
```
In the code above, we have created a component to encapsulate the progress bar UI logic. We ensured accessibility by setting the `progressbar` value on the `div` tag `role` attribute. We also provided the values for the `aria-valuenow` and `aria-valuemax` attributes. Let's define the CSS properties for a visually appealing progress bar:
```css
/* progressbar.css */
#progress {
  height: 2rem;
  width: 100%;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}

#progress > p {
  font-size: 12px;
  position: relative;
  z-index: 2;
}

#indicator {
  top: 0;
  position: absolute;
  left: 0;
  height: 100%;
  background: #3a9a3a;
  transition: width 0.4s ease-out;
  -moz-transition: width 0.4s ease-out;
  -webkit-transition: width 0.4s ease-out;
}
```
In the CSS styles above, we set the width and height of the `#progress` selector. Ensuring the bar is wide enough for the indicator. The CSS `flexbox` values center the label in vertical and horizontal space. To display the `#indicator` selector, we defined its position as absolute. Furthermore, we set the top and left properties to zero and the height to 100%. These values enable the indicator to fit into the bar's dimension. Finally, defining the transition property facilitates a smooth repaint.

The following code renders the progress bar in the app:
```jsx
// App.jsx
import ProgressBar from "./ProgressBar";

export default function App() {
  return (
    <div className="App">
      <ProgressBar />
    </div>
  );
}
```
For demonstration purposes, we set an initial value of 50. Here is a snapshot of the rendered progress bar:
![A progress bar 50% filled](https://blog.openreplay.com/images/use-requestanimationframe-in-react-for-smoothest-animations/images/image1.png)
To animate the bar, let's replace the line that initializes the progress value. 
```jsx
// ProgressBar.jsx
import useProgressLoader from "./useProgressLoader";

const ProgressBar = () => {
  const { value } = useProgressLoader();
  return (
    { /* Progress bar UI JSX */ }
  );
};
```
In the code above, we used the value returned by the `useProgressLoader` hook instead. This value ensures the rendered horizontal displays a visual progress.

Below is a visual result of the horizontal progress bar in motion:
![A horizontal progress bar in motion](https://blog.openreplay.com/images/use-requestanimationframe-in-react-for-smoothest-animations/images/image2.gif)
### Creating a Circular Progress Indicator
A circular progress indicator is a UI element that provides visual feedback to convey the idea of an ongoing task or process by moving in a circular motion toward its starting position. Let's demonstrate this with the code example below:
```jsx
// CircularProgressLoader.jsx

import "./circular-progress-loader.css";
import useProgressLoader from "./useProgressLoader";

const CircularProgressLoader = () => {
  const { value } = useProgressLoader();
  return (
    <section className="circle-wrapper">
      <div
        className="circular-progress"
        role="progressbar"
        aria-valuemin={0}
        aria-valuenow={value}
        aria-valuemax={100}
        style={{
          background: `conic-gradient(#F4A443 ${(360 / 100) * value}deg, #F7F8F7 0deg)`,
        }}
      >
        <p aria-label="Generate progress value" className="progress-label">
          {value}%
        </p>
      </div>
    </section>
  );
};

export default CircularProgressLoader;
```
In the code snippet above, we employed three accessible elements. The innermost element displays the current fill percent. Furthermore, we applied the CSS conic-gradient background value to create a color-filled area. Finally, incorporating the value from the `useProgressLoader` hook, animated the loader. Let's define the CSS properties for a circular area:
```css
/* circular-progress-loader.css */

.circular-progress {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 300px;
  width: 300px;
  border-radius: 50%;
}

.circular-progress .progress-label {
  position: absolute;
  z-index: 2;
}
```
To create a circular shape, we set equal width and height and `border-radius` of 50%. Defining the `flex-box` values centers the label in the horizontal and vertical space.

The following code displays the progress indicator:
```jsx
// App.jsx
import CircularProgressLoader from "./CircularProgressLoader";

export default function App() {
  return (
    <div className="App">
      <CircularProgressLoader />
    </div>
  );
}
``` 
Below is a visual illustration of the circular progress indicator in motion:
![A progress indicator moving in a circular motion](https://blog.openreplay.com/images/use-requestanimationframe-in-react-for-smoothest-animations/images/image3.gif)
## Final Thoughts
This article explored how the `requestAnimationFrame` method surpasses conventional means. We also discussed its role and benefits in web animation. Furthermore, we ensured modularity and reusability of animation logic with a custom hook.

As a powerful tool, the `requestAnimationFrame` method offers several benefits and use cases. Understanding its proper usage is crucial in optimizing animations and enhancing user experience.