In our journey to avoid jank, one area that can potentially cause problems is how we use JavaScript to do animations. Ideally, our animation frame rates would match the refresh rate of the monitor, since if the browser’s not able to keep up, it can be noticeable (here’s a demo of what different frame rates look like compared with each other). In order to match the 60fps goal, we’ll need to make sure animations are updated every 16.7ms or so (1s / 60fps = 16.7ms). But how do we ensure that these animations take place as that rate? Today, we’ll look at some options that have been used in the past, as well as a more modern approach that has become standard in modern browsers.
Early Options
For many years, the main options were to use the setTimeout()
or the setInterval()
function. We could call either of these and set the interval to match our desired refresh rate.
setTimeout()
With setTimeout()
, we could chain animations together, having the next one start a specified amount of time after the completion of the current one.
(function myAnimation() {
// do animation
setTimeout(myAnimation, 1000/60);
})();
In this case, the myAnimation() function uses setTimeout()
to recursively call itself 16.7ms (1000/60) after it completes.
setInterval()
An alternative to chaining each animation to the completion of a previous one is to set up the browser to call animations at a certain pre-set interval, regardless of the state of the previous animation. This can be done with setInterval()
.
function myAnimation() {
// do animation
}
setInterval(function() {
myAnimation();
}, 1000/60 );
Here, we set up the myAnimation() function, and have browser call it every 16.7ms.
One of the benefits of the setInterval()
method over the setTimeout()
was that you could have a single function control all of your animations, and then have that function called at a set rate, instead of having multiple animations calling themselves at different times.
Both of these options work to a certain degree—and are what developers used for many year—but there are a couple shortcomings to these approaches.
Challenges
-
The primary issue is these function don’t take into account what else is going on in the browser. They don’t know, or care, if the animation will be visible, if the page is in an inactive tab, etc. And so the animations would continue to be called, even when they may not be necessary.
-
Similarly, they will fire the animation at their own discretion, regardless of whether the the browser is able to render the animations optimally or not. Especially if the timing of these calls aren’t in sync with the refresh rate, the efficiency of the browser is going to suffer. Even if the browser can keep us, it’s going to have to work much harder to do so, which affects CPU usage, baterry life, and the like.
A Better Alternative
Thankfully, a few years ago browser makers began to introduce the requestAnimationFrame API. According to MDN’s explanation, “The window.requestAnimationFrame() method tells the browser that you wish to perform an animation and requests that the browser call a specified function to update an animation before the next repaint.”
(function myAnimation() {
requestAnimationFrame(myAnimation);
// other code for animation
})();
In this example, we set up the myAnimation() function to do the animation, and then pass this function to requestAnimationFrame()
. In doing so, we give this function to browser, and request that it be called before the next repaint. In the example above, we can chain these requests together recursively like we did in the setTimeout()
example, but we don’t specify the delay. We leave that to the browser—which is what makes the requestAnimationFrame(
) approach so much more efficient.
Benefits
The essential difference here is that requestAnimationFrame()
defers to the browser for how and when to do the animations.
- The browser has the ability to optimize when these animations are rendered. For instance, grouping all of our animations into a single browser repaint. This not only saves CPU cycles, but ensures that everything stays in sync.
- The browser can also throttle or stop animations in inactive tabs, making it more CPU friendly, and thus more battery-friendly
We’re calling for the same animations as we were before, but now we’re giving the browser complete control as to how to do these animations. We’re essentially saying, “Browser, you know what’s going on, and the other demands being placed on you. Here are the animations that need to be done. Use your discretion as to how to do them most efficiently.”
Over the years, it has become well supported by the various browsers. There are few older browsers that don’t support it (like IE 9 and earlier, and Android 4.3 and earlier), but there is a requestAnimationFrame polyfill by Erik Möller, Paul Irish, and Tino Zijdel that will help standardize the syntax for older browsers.
Choosing Efficiency
By using requestAnimationFrame()
we can keep using the same animation functions as before, but can now offload the responsibility of keeping everything in sync. The browser can handle that more efficiently than we can, which frees us up to work on other things. It also gives us a better shot at keeping our animations at the target 60fps and avoiding the jank that sometimes accompanies in-browser animations.
Resources
- Window.requestAnimationFrame(), on MDN
- requestAnimationFrame for Smart Animating, by Paul Irish
- requestAnimationFrame, by creativejs