Skip to content

The requestAnimationFrame() guide

New Course Coming Soon:

Get Really Good at Git

Learn the API to perform animations and schedule event in a predictable way

requestAnimationFrame() is a relatively recent browser API. It gives a more predictable way to hook into the browser render cycle.

It’s currently supported by all modern browsers (and IE 10+)

Browser support

It’s not an API specific to animations, but that’s where it is used the most.

JavaScript has an event loop. It continuously runs to execute JavaScript.

In the past, animations were performed using setTimeout() or setInterval(). You perform a little bit of an animation, and you call setTimeout() to repeat again this code in a few milliseconds from now:

const performAnimation = () => {
  //...
  setTimeout(performAnimation, 1000 / 60)
}
setTimeout(performAnimation, 1000 / 60)

or

const performAnimation = () => {
  //...
}
setInterval(performAnimation, 1000 / 60)

You can stop an animation by getting the timeout or interval reference, and clearing it:

let timer

const performAnimation = () => {
  //...
  timer = setTimeout(performAnimation, 1000 / 60)
}

timer = setTimeout(performAnimation, 1000 / 60)

//...

clearTimeout(timer)

The 1000 / 60 interval between the performAnimation() calls is determined by the monitor refresh rate, which is in most of the cases 60 Hz (60 repaints per second), because it’s useless to perform a repaint if the monitor cannot show it due to its limitations. It leads to ~16.6ms of time we have at our disposal to display every single frame.

The problem with this approach is that even though we specify this precision accurately, the browser might be busy performing other operations, and our setTimeout calls might not make it in time for the repaint, and it’s going to be delayed to the next cycle.

This is bad because we lose one frame, and in the next the animation is performed 2 times, causing the eye to notice the clunky animation.

Check this example on Glitch of an animation built using of setTimeout().

requestAnimationFrame is the standard way to perform animations, and it works in a very different way event though the code looks very similar to the setTimeout/setInterval code:

let request

const performAnimation = () => {
  request = requestAnimationFrame(performAnimation)
  //animate something
}

requestAnimationFrame(performAnimation)

//...

cancelAnimationFrame(request) //stop the animation

This example on Glitch of an animation built using of requestAnimationFrame() shows how.

Optimization

requestAnimationFrame() since its introduction was very CPU friendly, causing animations to stop if the current window or tab is not visible.

At the time requestAnimationFrame() was introduced, setTimeout/setInterval did run even if the tab was hidden, but now since this approach proved to be successful also to battery savings, browsers also implemented throttling for those events, allowing max 1 execution per each second.

Using requestAnimationFrame the browser can further optimize the resource consumption and make the animations smoother.

Timeline examples

This is the perfect timeline if you use setTimeout or setInterval:

Perfect timeline

you have a set of paint (green) and render (purple) events, and your code is in the yellow box - by the way, these are the colors used in the Browser DevTools as well to represent the timeline:

The devtools timeline

The illustration shows the perfect case. You have painting and rendering every 60ms, and your animation happens in between, perfectly regular.

If you used a higher frequency call for your animation function:

Too frequent timeline

Notice how in each frame we call 4 animation steps, before any rendering happens, and this will make the animation feel very choppy.

What if setTimeout cannot run on time due to other code blocking the event loop? We end up with a missed frame:

Missed frame

What if an animation step takes a little bit more than you anticipate?

Delay in the timeline

the render and paint events will be delayed as well.

This is how requestAnimationFrame() works visually:

Delay in the timeline

all the animation code runs before the rendering and painting events. This makes for a more predictable code, and there’s a lot of time to do the animation without worrying about going past the 16ms time we have at our disposal.

Check this great video by Jake Archibald on the topic.

Are you intimidated by Git? Can’t figure out merge vs rebase? Are you afraid of screwing up something any time you have to do something in Git? Do you rely on ChatGPT or random people’s answer on StackOverflow to fix your problems? Your coworkers are tired of explaining Git to you all the time? Git is something we all need to use, but few of us really master it. I created this course to improve your Git (and GitHub) knowledge at a radical level. A course that helps you feel less frustrated with Git. Launching May 21, 2024. Join the waiting list!
→ Read my DOM Tutorial on The Valley of Code
→ Read my Browser Events Tutorial on The Valley of Code
→ Read my Browser APIs Tutorials on The Valley of Code

Here is how can I help you: