requestanimationframe Example: The optimal way to animate!

In this article, we are going to have a look at an example of using requestanimationframe in order to animate something on the screen.

Besides this, you will learn how to pause and resume the animation and also reset it.

First, let’s have a look at a working example…

A Working Example

See the Pen
requestAnimationFrame How To Stop & Play
by Khoj Badami (@livefiredev)
on CodePen.

The above example works by simply updating the CSS transform rotate property of a “div” as the animation progresses.

Below, we will go through the code line by line.

First, what Is requestanimationframe?

From the MDN website, here is what requestanimationframe is all about..

The method tells the browser that you wish to perform an animation and requests that the browser calls a specified function to update an animation before the next repaint.

Okay, so let’s try to break that down. Before we look at anything else, we need to understand what does “the next repaint” mean.

“The Next Repaint” And What That Means

By the name, you can probably guess. It just means that as the user interacts with the website, the browser needs to replace the “pixels” on the screen and paint in new pixels.

Some of the reasons to paint new pixels might be:

  • A gif is running
  • Scroll is happening
  • The colour of elements are changing on hover
  • Elements are being hidden and shown
  • Many other things…

Below, I have created a little video of all the things that get repainted (highlighted in green) as the user browses Reddit.

If you want to look at this yourself of any website, you can do this using Chrome Dev tools. Details for how to do this are here.

With that clear, let’s go back to understanding what requestanimationframe actually does.

So, what does requestanimationframe help you do?

Basically, it’s a way of telling the browser:

You: Hey buddy. You are going to have to paint new pixels on the screen soon. Before you do that, can you run this function and change the state of the elements on the screen just a little bit. That way, you can update this along with eveything else.

Animating With requestAnimationFrame

The Usual Structure Of A requestAnimationFrame Animation

The code below is from the working demo right on top of the page.

Usually, the structure of the code has 4 parts:

  1. Set up the state of the animation: For example: Current angle of rotation of a box & if the animation is currently running
  2. Create a function which moves the animation into the next frame
  3. Call “requestAnimationFrame” from inside the function IF the animation needs to continue.
  4. Start the animation from outside the function by calling requestAnimationFrame the first time.

Let’s look at all the 4 parts in the code snippet below. I have written comments to mark the 4 parts.

// 1: Set up the animation intial state
let angle = 0;
let animate = true;

// 2: A function that moves the animation into the next frame
let updateBoxAngle = () => {
    angle += 1;
    document.getElementById("box").style.transform = `rotate(${angle}deg)`;

    // 3: Calling requestAnimationFrame from inside the function
    // if the animation needs to continue
    if (animate == true) { 
        requestAnimationFrame(updateBoxAngle) 
    }
}

// 4: Get the animation started by calling requestAnimationFrame first time
requestAnimationFrame(updateBoxAngle);

As you can see in the above on line 3, we have defined a variable called “animate”. This is a boolean that holds the value which decides if we should continue the animation or not.

The Key To Understanding How To Pause / Stop requestAnimationFrame

If you see the MDN page on requestAnimationFrame you will see the following note highlighted..

Note from MDN about requestAnimationFrame that helps with stop / pause & play

As you can see, if you want to continue the animation, you need to call requestAnimationFrame yourself again. And so, all you need to do to stop the animation is NOT call requestAnimationFrame.

let updateBoxAngle = () => {
    angle += 1;
    document.getElementById("box").style.transform = `rotate(${angle}deg)`;

    // Calling requestAnimationFrame from inside the function
    // if the animation needs to continue
    if (animate == true) { 
        requestAnimationFrame(updateBoxAngle) 
    }
}

The condition on line 7 is to do just that. We just need to play with the value to “animate” in order to pause and play the animation.

How To Pause / Stop / Kill The Animation Recursion?

document.getElementById("pauseButton").addEventListener('click', () => {
    animate = false;
});

It’s simple, we will just update the value of “animate” to false.

How To Resume The Animation Recursion?

document.getElementById("playButton").addEventListener('click', () => {
    if (animate == false) {
        animate = true;
        requestAnimationFrame(updateBoxAngle);
    }
});

All we do here is check to see if the animation is really paused and then if it is, we set “animate” to true again and kick off the animation using requestAnimationFrame.

IMPORTANT NOTE: If you do not check if the animation is already running, you will double up the speed of the animation because requestAnimationFrame will start to get called twice.

How To Reset The Animation Recursion

We set the “state” of the animation back to the original state. Then just to give the user a little bit of visual feedback, we wait for a second (1000 milliseconds) and then we kick off the animation again…

document.getElementById("resetButton").addEventListener('click', () => {
    angle = 0;
    animate = false;

    setTimeout(function() {
        animate = true;
        requestAnimationFrame(updateBoxAngle);
    }, 1000)
});

What’s So “Optimal” About requestAnimationFrame? Why Not Just Use setInterval?

In theory, you could just use setInterval and use that to animate anything you need on the screen. Then why use something new?

Well, there are 2 main things you get in terms of “optimality” when using request animation frame:

1: Screens Refresh Rate Is Taken Into Account For Free

Refresh rate is defined as:

the frequency with which the image on a computer monitor or similar electronic display screen is refreshed, usually expressed in hertz.

That means that if your screen / monitor is not going to update more than 60 times a second, it makes no sense at all to update the animation more than 60 times a second.

requestAnimationFrame is simply not going to be run more than the refresh rate of the monitor. This saves CPU, battery and many other good things for your users.

2: The Browser Does Not Call requestAnimationFrame If No One Is Looking

This is once again an optimization for speed, CPU, battery and better performance of all the other browser tabs.

Since requestAnimationFrame is giving the control over to the browser, it runs it less often when nobody is looking. You might not be looking if you have switched over to other tabs. Or maybe the browser is running but is minimized. The browser can take all this into account.

I made a little demo for you to try and play with this and see what the browser might by doing…

See the Pen
setInvterval vs requestAnimationFrame
by Khoj Badami (@livefiredev)
on CodePen.

In the demo, there are 2 circles. Both of them are being rotated. One by requestAnimationFrame and the other by setInterval.

The difference between how much both have been rotated has been tracked on top (Diff)

If keep the demo above running in one tab and then come back to it after some time, you might see a big difference between the two angles if your browser decided that there was no point in running the animation while you were not looking.

I have noticed times when this happens and also times when this does not happen. So, it seems like the browser does this optimization when it feels like it needs to.

Note: On line 21 of the JS is where you can set the “refresh rate” of your screen. It’s currently set to (1000 / 58.5 around 60 Hz)

Conclusion

Hope you found this helpful. Hope the requestAnimationFrame example and code give you some guidance on how you can start to use it in your projects.