The KIRUPA orange logo! A stylized orange made to look like a glass of orange juice! Tutorials Books Videos Forums

Customize Theme


Color

Background


Done

Table of Contents

Animating with requestAnimationFrame

by kirupa   |   filed under Web Animation

When you are creating an animation, it goes without saying that you want your animation to run smoothly and fluidly when someone is viewing it. If you are using CSS Animations, then you have nothing to worry about. Your browser takes care of everything for you. If you are creating your animation using JavaScript, things are a bit different. Instead of the browser giving you some assistance, you are left on your own. It is up to you to make sure your animation runs smoothly while at the same time taking into account the various factors that could affect your animation's performance. Factors such as other things happening on the page. Factors such as your laptop / phone / tablet going into battery mode and halving its performance reduced. Factors such as another tab taking focus. You get the point.

Times have changed. Your prayers have been answered. You now have help in the form of the requestAnimationFrame function that allows you to create smooth and fluid animations in JavaScript without...you actually having to worry about making it smooth and fluid. Just add a few calls to requestAnimationFrame and your browser takes care of the rest. That's it.

In case you are still skeptical whether this function will do what I claim, below is an example of a JavaScript-based animation that I created using requestAnimationFrame:

I'm not using functions like setInterval or setTimeOut to create my animation loop or anything like that. It's all taken care of by requestAnimationFrame, and in this tutorial, you will learn all about this magical function and how you can use it to create smooth animations.

Let's get started!

Meet requestAnimationFrame

Making your animations run smoothly depends on a lot of factors. It depends on what else is going in your page. It depends on what other animations might be running in parallel. It depends on whether the user is interacting with the page by clicking or typing. It depends on what browser you are using and when it decides to repaint or update what is shown on the screen.

Traditionally, you may have used a function like setInterval or its funnier cousin setTimeOut to power your animation loop. The problem with these two functions is simple. They don't understand the subtleties of working with the browser and getting things to paint at the right time. They have no awareness of what is going on in the rest of the page. These qualities made them very inefficient when it came to powering animations because they often request a repaint/update that your browser simply isn't ready to do. You would often end up with skipped frames and other horrible side effects.

If setInterval and setTimeOut went out on a date, they would probably be heckled quite a bit. Despite how bad setInterval and setTimeOut were for working with animations, you had no real alternatives. You had to use them. This lack of better alternatives left animations created via JavaScript looking a little less polished when compared to animations created in CSS or via a dedicated runtime like Flash.

Fortunately, things changed. Given how important animations are for creating great looking applications and sites, the major browser vendors (starting with Mozilla) decided to address this problem in an elegant way. Instead of burdening you with having to deal with all of the issues related to creating a smooth animation in JavaScript using setInterval or setTimeOut, they created a function called requestAnimationFrame that handles all of those issues for you.

What makes requestAnimationFrame so awesome is that it doesn't force the browser to do a repaint that may never happen. Instead, it asks the browser nicely to call your animation loop when the browser decides it is time to redraw the screen. This results in no wasted work by your code on screen updates that never happen. Skipped frames are a thing of the past. Best of all, because requestAnimationFrame is designed for animations, your browser optimizes the performance to ensure your animations run smoothly depending on how much system resources you have available, whether you are running on battery or not, whether you switch away to a different tab, and so on.

Words haven't been invented in the English language to describe how awesome the requestAnimationFrame function is.

Using This Magical Function

The way you use requestAnimationFrame is very simple. Whenever you want to redraw your screen, simply call it along with the name of your animation loop function (aka a callback) that is responsible for drawing stuff to your screen:

requestAnimationFrame(callback);

The thing to note is that the requestAnimationFrame function isn't a loop. It isn't a timer. You need to call it every time you want to get the screen repainted. This means, unless you want your animation to stop, you need to call requestionAnimationFrame again through the same callback function that you specified. I know that sounds bizarre, but it looks as follows:

function animate() {

  // stuff for animating goes here

  requestAnimationFrame(animate);
}
animate();			

The animate method is the callback function for our requestAnimationFrame call, and it will get called very rapidly once it starts running.

Vendor Prefixes Not Needed

At this point, requestAnimationFrame has extremely broad support among the browsers people use according to the caniuse statistics for it. There is no need to vendor prefix it anymore, so I would suggest saving some lines of code from having to do that.

 

Simple Example

Towards the top of this page, you saw an example of an animation with circles buzzing around a fixed point. You can also view it in a new window if you want to see it in isolation.

The script for making that animation work looks as follows with the requestAnimationFrame-related lines highlighted:

let mainCanvas = document.getElementById("myCanvas");
let mainContext = mainCanvas.getContext('2d');

let canvasWidth = mainCanvas.width;
let canvasHeight = mainCanvas.height;

// this array contains a reference to every circle that you will create
let circles = new Array();

//
// The Circle "constructor" is responsible for creating the circle objects and defining the various properties
// they have
//
function Circle(angle, sign, radius, rotationRadius, initialX, initialY) {
	this.angle = angle;
	this.sign = sign;
	this.radius = radius;
	this.rotationRadius = rotationRadius;
	this.initialX = initialX;
	this.initialY = initialY;
	this.incrementer = .01 + Math.random() * .1;
}

Circle.prototype.update = function () {

	this.angle += this.incrementer;
	
	this.currentX = this.initialX + this.rotationRadius * Math.cos(this.angle);
	this.currentY = this.initialY + this.rotationRadius * Math.sin(this.angle);
	
	if (this.angle >= (Math.PI * 2)) {
		this.angle = 0;
		this.incrementer = .01 + Math.random() * .1;
	}

	// The following code is responsible for actually drawing the circle on the screen
	mainContext.beginPath();
	mainContext.arc(this.currentX, this.currentY, this.radius, 0, Math.PI * 2, false);
	mainContext.closePath();
	mainContext.fillStyle = 'rgba(177, 0, 129, .1)';
	mainContext.fill();
};

//
// This function creates the circles that you end up seeing
//
function createCircles() {
// change the range of this loop to adjust the number of circles that you see
	for (let i = 0; i < 50; i++) {
		let radius = 5 + Math.random() * 40;
		let initialX = canvasWidth / 2;
		let initialY = canvasHeight / 2;
		let rotationRadius = 1 + Math.random() * 30;
		let angle = Math.random() * 2 * Math.PI;
		
		let signHelper = Math.floor(Math.random() * 2);
		let sign;
		
		// Randomly specify whether the circle will be rotating clockwise or counterclockwise
		if (signHelper == 1) {
			sign = -1;
		} else {
			sign = 1;
		}
		
		// create the Circle object
		let circle = new Circle(angle, sign, radius, rotationRadius, initialX, initialY);
		circles.push(circle);
	}
	
	// call the draw function approximately 60 times a second
	requestAnimationFrame(draw);
}
createCircles();

function draw() {
	mainContext.clearRect(0, 0, canvasWidth, canvasHeight);
	mainContext.fillStyle = '#F6F6F6';
	mainContext.fillRect(0, 0, canvasWidth, canvasHeight);
	
	for (let i = 0; i < circles.length; i++) {
		let circle = circles[i];
		circle.update();
	}
	
	// call the draw function again!
	requestAnimationFrame(draw);
}

This all seems like a lot of code, but don't worry about all of that for now. Instead, just pay attention to the highlighted lines. In the two highlighted lines, we call requestAnimationFrame with the draw function specified as the callback.

Below is that code with everything extraneous omitted:

function createCircles() {
	   .
	   .
	   .
	requestAnimationFrame(draw);
}
createCircles();

function draw() {
	  .
	  .
	  .
	requestAnimationFrame(draw);
}

Notice that you are essentially creating a loop where you are calling the draw function using the requestAnimationFrame, and inside the draw function, you have another requestAnimationFrame call with the same draw function specified. You don't have to use requestAnimationFrame to make the first call to the draw function. You could have just called draw directly just like I called the animate method a few sections earlier. There is no right or wrong way to do this, but by using requestAnimationFrame even for the initial call, you are deferring when to call the draw method to your browser as opposed to forcing it to be called instantaneously.

Inside the draw method, while this looks like it might cause an infinite loop, your browser knows what to do and ensures that nothing bad like that happens. Don't try something like this with your own vanilla functions, though :P

Your Frame Rate

So far, I've been extolling the virtues of requestAnimationFrame and how it can make your animations really smooth. The word smooth isn't really a good way to measure something. It won't hold up under scrutiny! The way we measure smoothness is by using a number you are probably very familiar with called the frame rate. You are probably already familiar with frames rates from watching movies or playing video games where you are told that the higher your frame rate, the better the result is. Guess what? That same holds for us in the HTML world also.

With requestAnimationFrame, your frame rate is typically around 60 frames per second (FPS). To repeat that differently, this means your requestAnimationFrame function and related code have the potential to refresh your screen 60 times every second. This number wasn't arbitrarily chosen. It is the upper limit on how quickly your laptop screen, computer monitor, phone display, etc. can physically update your screen.

Your Frame Rate May Go Lower than 60 FPS

If your animation loop is very complex and does a lot of work or your browser is swamped with other things, your frame rate will be lower than 60 frames per second. In general, your browser will do the right thing to ensure your animation is extremely smooth, but be prepared for some random slowdowns.

Now, there will be times when you may want to deliberately slow your animation down. You may not need your animation loop getting called 60 times every second. If you want to throttle your animation's speed, you can do something like the following:

let framesPerSecond = 10;

function animate() {
    setTimeout(function() {
        requestAnimationFrame(animate);

        // animating/drawing code goes here


    }, 1000 / framesPerSecond);
}

Notice that we are using setTimeout to delay when the next requestAnimationFrame call gets made. You can see an example of this code being used in the Letter Cycler example where we are deliberately slowing our animation down to 10 FPS.

Stopping Your Animation Loop

Once your requestAnimationFrame loop starts running, rarely will you ever need to tell it to stop. If you do need to stop your animation loop from doing unnecessary work, you can do something like the following:

let running = true;

function animate() {
	if (running) {
		// do animation or drawing stuff
		
	}
	requestAnimationFrame(animate);
}

If your running variable were to ever be set to false, your animation loop will stop doing whatever work is being done. Your animate function will still get called by virtue of it being attached to your requestAnimationFrame. It just won't be doing any work since the if (running) check will return false.

Now, if you really REALLY need to stop your requestAnimationFrame from calling some poor function around 60 times a second, you do have requestAnimationFrame's evil twin, cancelAnimationFrame. The best way to explain how it prevents requestAnimationFrame from working is by looking at a simple example:

// store your requestAnimatFrame request ID value
let requestId;

// setting up a click event listener
let bodyElement = document.querySelector("body");
bodyElement.addEventListener("click", stopAnimation, false);
 
function animate() {

	// doing some animation stuff
	
	// get the requestID as part of calling animate()
	requestId = requestAnimationFrame(animate);
}
animate();

function stopAnimation(e) {
	// use the requestID to cancel the requestAnimationFrame call
	cancelAnimationFrame(requestId);
}

This simple example should readily highlight how the cancelAnimationFrame works. The thing that I didn't call out about requestAnimationFrame is that it returns an ID value whenever it gets called:

requestId = requestAnimationFrame(animate);

Normally, you don't care about this ID. The only time you really need to know this ID value is when wanting to use cancelAnimationFrame. The reason is that cancelAnimationFrame uses the ID value to identify the right requestAnimationFrame function to stop:

cancelRequestAnimationFrame(requestId);

That's all there is to the cancelAnimationFrame function. I should emphasize that you really don't need to go through all this trouble to cancel a requestAnimationFrame setup. The initial approach I outlined with the running variable is a good enough approach to use. With that said...if you really want to go the extra mile, you have a friend in cancelAnimationFrame!

The Timestamp Argument

The last thing we are going to look at is the argument requestAnimationFrame passes in to the callback function. The following is the callback function signature I showed you earlier:

function draw() {
	  .
	  .
	  .
	requestAnimationFrame(draw);
}

That is still accurate and totally works. There is an optional argument representing the exact time the callback function is called (aka a timestamp) that I kinda did not cover, and a big thanks to Jamie Perkins (@inorganik) for pointing that out!

Below is the slightly different callback function signature with the timestamp argument specified:

function draw(timeStamp) {
	  .
	  .
	  .
	requestAnimationFrame(draw);
}

You can use any name for this argument (I went with timeStamp), and also notice that I didn't change anything about how I call this callback function. It is still requestAnimationFrame(draw).

Anyway, what this timestamp argument represents is a time value accurate to a thousandth of a millisecond for when the callback function was called. If you inspect the value of the timestamp, everytime your callback function gets called, you'll see time values that look similar to the following appear:

.
.
.
1390549443371.2961
1390549443442.2961
1390549443453.2961
1390549443489.2961
1390549443503.2961
1390549443520.2961
1390549443536.2961
1390549443554.296
1390549443569.2961
1390549443586.2961
1390549443604.2961
1390549443619.2961
1390549443636.2961
1390549443653.2961
1390549443670.2961
1390549443686.2961
1390549443704.2961
1390549443719.2961
1390549443736.2961
.
.
.

At this point, this may not make much sense, and you may also be wondering why this is important. The simple answer is that you have a really high precision counter that you can use to keep track of how many times your callback function was called. To fully see the importance of this, I will explain in greater detail in a future article. In the meantime, the Animating with Robert Penner's Easing Functions tutorial and the Incrementer vs. Timestamp examples may provide some clues.

Conclusion

All in all, this is pretty exciting. The requestAnimationFrame function brings to the table the same level of optimization your animations or transitions created in CSS have. Instead of your code telling the browser to redraw the screen and the browser (being the temperamental thing that it is) ignoring that request, the requestAnimationFrame politely asks the browser to call the animation loop when it is ready to redraw the screen. This cordial relationship results in really smooth animations. See! Being nice always pays off...if you are a JavaScript function!

Just a final word before we wrap up. If you have a question and/or want to be part of a friendly, collaborative community of over 220k other developers like yourself, post on the forums for a quick response!

Kirupa's signature!

The KIRUPA Newsletter

Thought provoking content that lives at the intersection of design 🎨, development 🤖, and business 💰 - delivered weekly to over a bazillion subscribers!

SUBSCRIBE NOW

Serving you freshly baked content since 1998!
Killer icons by Dark Project Studios

Twitter Youtube Facebook Pinterest Instagram Github