JavaScript

Making smooth animations with JavaScript's requestAnimationFrame

If you ever animated anything with JavaScript then you know that it is a very manual process. You call a function and make your changes incrementally until the animation is completed.

With CSS3 you can make animation, but still JavaScript offers a wider range of control. So today we will work on a simple fade in, fade out animation, very similar to the one found in jQuery.

What it does is change the opacity of an element until it becomes invisible. Then we make the actual element disappear all together form the page flow.

One easy mistake most beginners do, I know I did, is to use a for loop to incrementally make changes to the CSS property. The problem is JavaScript evaluates the whole loop first before updating the DOM. So something like this will not work:

for (var i = 100; i > 0;){
    i--;
    div.style.opacity = (i/100);
}

This automatically sets the opacity to 0. Rightly so, because you wouldn't be able to control the speed of this animation anyway. The way solve this problem before HTML5 supporting browsers was to use setInterval or setTimeout functions in JavaScript.

Interval

// With setInterval
var speed = 1000/60; // 60 Frame per second
var opacity = 100;
var timerId = setInterval(function() {
    opacity--;
    div.style.opacity = opacity/100;
    if (opacity >= 0){
        clearInterval(timeId);
        console.log("Look at me, I'm done!");
    }
},speed);

Timeout

// With setTimeout // my favorite
var speed = 1000/60; // 60 Frame per second
var opacity = 100;
function fadeOut() {
    opacity--;
    div.style.opacity = opacity/100;
    if (opacity > 0){
        setTimeout(fadeOut,speed);
    }
}
fadeOut();

I prefer setTimeout in this case because there are more chances of running into problems with setInterval. If you pass a function that is too complex in setInterval, there are chances it won't complete before the time allocated and JavaScript will call the function one more time. You will end with functions called in parallel*.

(*) Javascript doesn't run it in parallel but the functions may run in an unpredictable order.

There is no problem per se with these methods but there is a way to make them more efficient.

The browser re-renders the page every time there is change. For example when you scroll, the page is redrawn with the new coordinates. If you are scrolling the page and the fade animation is going, the browser not only renders for the scroll, but it also renders for your animation. The two redraws of the page are out of sync, thus using extra resources.

The solution for that is the new JavaScript method requestAnimationFrame.

You pass it your animation function, and it syncs it with the next redrawing of the page, thus ensuring a smooth animation and maximum frame per second possible.

Here is how we can modify our code to take advantage of it:

// With requestAnimationFramet // my super favorite
var opacity = 100;
function fadeOut() {
    opacity--;
    div.style.opacity = opacity/100;
    if (opacity > 0){
        requestAnimationFrame(fadeOut);
    }
}
requestAnimationFrame(fadeOut);

It is very similar to setTimeout. In fact, since it is not supported in older browsers, we use setTimeout as a fallback:

if (!window.requestAnimationFrame) {
    window.requestAnimationFrame = function(fn){
        setTimeout(fn,16.66);
    }
}

It is a good idea to use the method for any type of animation for maximum frame rate. And it seamlessly integrate with older browsers.

Originally Posted on Mar 1, 2015 @ 1:32


Comments(4)

Azer :

Really good stuff. But I don't know how to set custom interval using requestanimationframe?

Thanks in advance

Ibrahim :

Hi @Azer,

with requestAnimationFrame you don't need to set an interval. you can always set an artificial framerate, let's say 16.67ms for 60 fps and check how long the function the function takes to run and adjust the values.

Example:

var fps = 1000/60;
var time = (new Date()).getTime();
var speed = 10;
function animate() {
   var rate = ((new Date()).getTime() - time)/ fps;
   div.style.top = (speed * rate)+"px";

   requestAnimationFrame(animate);
}
animate();

See how the speed is adjusted by the rate which depends on the framerate you set.

Azer :

thanks very much

Shaedo :

2019 and your article is still incredibly helpful. Thank you.

Let's hear your thoughts

For my eyes only