requestAnimationFrame was one of the things that scared me as a JavaScript developer, not because it was too complex but because it sounded too fancy to try. requestAnimationFrame function has nothing to do with animation, trust me. You can achieve other things as well with this, you will see it soon.

Let’s talk about what animation is. The animation is nothing but an illusion because things presented to us are in frames and not in continuous motion. If you were born in the 20th century, then you might have watched films in movie theatre which were recorded on and projected from actual plastic films. Minimum 24 frames should be displayed one after another in just 1 second to create an illusion of motion. This doesn’t only apply to only films but everyday screens and projectors. The standard frame rate for the NTSC broadcast is 23.975 FPS while for the PAL it is 25 FPS.

So we understood that, in order to create an illusion of motion or animation, you need to move an object at a minimum rate of 24 FPS. But that doesn’t mean smooth animation though. Smooth animation is created with an infinite frame rate but since that’s not possible and our brain doesn’t detect that level of frame rate, it’s safe to say that 60 FPS is good enough. Most of the modern devices like your laptop or a smartphone refreshes the screen at the rate of 60 FPS while some gaming systems support 120 FPS.

But what is a frame? There is no absolute definition of the frame and it varies depending on in which context you are using it. If you are talking about film tape, then a frame depends on recording FPS. If you are recording a video and camera is set at 30 FPS, then 30 individual film frames or photos will be generated per second which you have to play at 30 FPS. But when we are talking about the web, then this definition changes.

In web animation, you can move an object by 1px (on the device screen) or more. Lesser the pixels by which you are moving an element (DOM element), smoother the animation. Here the frame is a snapshot of the position of that element in time on the screen. In one second, if an element moves 60px by 1px at a time, then FPS will be 60. The same can be said if that element moves 120px by 2px at a time, but then animation won’t be smoother because the element is moving by a greater distance.

Alright, then how can we animate DOM elements using JavaScript? The solution is setInterval function in JavaScript. setInterval function calls a callback function every n milliseconds and we have to pass that n milliseconds as second argument after callback function. Let’s do that.

If you need to achieve 60 FPS, that means you need to move an element 60 times in a second. That means, the element has to move in roughly about (1000 ms /60 frames) = 16.7 ms . Let’s move our DOM element 1px in callback execution while callback needs to be called at every 16.7ms.

From above, we successfully created a smooth animation. But there is a huge problem that you might not be able to see on your PC, laptop or smartphone.

setInterval is not guaranteed to be called at the given delay n . Read this Mozilla documentation to know more about why that happens. In nutshell, setInterval is a Web API that defers the execution of some callback function. But a callback function is always blocking. This means if our web page is busy doing something else, this callback has to wait until the stack is empty. Read my article about how JavaScript works, there you will be able to understand what stack is and how Web APIs work. Not only that, execution of the callback function may take longer than the specified delay of 16.7ms, which means, our animation will run more than 1 second (but callback executes 60 times) and 60FPS won’t be achieved.

Have you heard the term frame-loss? A browser can refresh its screen at a maximum some m number of times per second. That number m depends on the screen refresh rate of your computer, the specified refresh rate of your browser, and how fast your CPU or GPU is. If your browser has the capability to refresh the screen at only 30 FPS (due to one or more above reasons), then there is no value addition in running the animation at 60 FPS. Your extra frame may get lost. Meanwhile, you are making changes to the DOM structure (layout) more times than your browser can render which also known as layout thrashing because these operations are synchronous and hamper your website performance as well as paint operations, resulting in poor animations.

Browser only refreshes screen where there is style change, layout change or reflow.

Hence you need some sort of callback function from the browser that tells you when next screen refresh or more accurately when next paint operation will be performed. This is nothing but your requestAnimationFrame Web API.

As now you know that the rAF is a Web API, that means the callback will be called asynchronously. Unlike setInterval , requestAnimationFrame does not accept delay argument, instead, it only calls the callback function when the browser is ready to perform the next paint operation. Hence, it’s our responsibility to move a DOM element in that callback function.

Let’s see our earlier example with requestAnimationFrame function.

You might have noticed some differences between setInterval and the requestAnimationFrame call. First, requestAnimationFrame does not get called automatically at every paint operation. You need to make a request every time when you make changes to the element. These calls will be stacked and executed one by one when the next paint operation is scheduled by the browser. These stacking can be done in a for loop or a while loop or more accurately in a recursive function.

The requestAnimationFrame returns an id of the request which is an integer. You can use that id to cancel the rAF request using the cancelAnimationFrame(id) which will throw away the execution of callback, thus stopping the animation (because we are using recursion here).

The rAF API does not guarantee to facilitate 60FPS animation, it is just a way to avoid frame loss and increase efficiency which will help you achieve more FPS. That means, if you are using how much pixels has moved to cancel the animation, for example, 60px in the above case, then depending on the refresh rate of your browser on your system, the animation can last for 1 second (60 FPS) or 2 seconds (30 FPS) or more.

So, how to make sure that whatever the FPS is, our animation must complete in 1 second and element should move 60px in that 1 second? This is where the callback function parameter comes into the picture.

When you pass a callback function to the requestAnimationFrame() call, rAF passes timestamp argument which is time elapse in milliseconds since web page was loaded (read more about here). timestamp gives the exact time when the callback is called.

So before coming up with a logic to solve this puzzle, we know things like duration (1 second) and distance (60px) for the animation. So, we need to calculate how much progress we have made in time and multiply that by 60px. Here progress is how much time has passed since the first callback execution.

progress = (startTime - timestamp) / duration

Using this progress value multiplied with distance, we get how much pixels a DOM element should be at the next paint operation.

position = distance * progress

As soon as progress reaches 100%, we need to stop calling requestAnimationFrame function. It could happen that progress is more than 100% because time elapsed since the start (the difference between the timestamp of first callback operation and the current one) could 980ms but next time it could be 1050ms. We can’t stop the animation at 980ms because we haven’t moved our element fully if we go by the above formula. That’s why we need a minimum of progress or 100 value.

safeProgress = Math.min(progress, 1); // 1 == 100%

It can also happen that position in the above formula is float because progress is float. But is it possible for pixels to be in decimal points? Here, we are calculating CSS pixels, which are very different from device pixels. Read this article to understand this concept.

CSS pixel is actually pixel density value, which means how much pixels on actual device you should occupy to render an object of one pixel mentioned in CSS. Hence, we can use the float value of CSS pixels and if you are lucky, then you will still be able to move/animate the DOM element by some pixels on the actual device. Having said this, let’s code.

We have used transform instead of marginLeft to utilize GPU driven animation.