Categories:

Understanding JavaScript's requestAnimationFrame() method for smooth animations

Created: Feb 9th, 2016

The modern web of today is filled with sights to behold on every page, where menus slide in and out, content gently fade into view, and elements animate around the screen as the user scrolls the page. While CSS3 has supplanted JavaScript in many cases to help power these rich animations in an intuitive, well optimized manner, JavaScript will always have a role to play in more complex scenarios that involve user interaction or non linear logic. It is because of this that the requestAnimationFrame() method was introduced, to help us execute animation related JavaScript code that make changes to the user's screen in an efficient, optimized manner. You've no doubt heard about this method before, though like many people may not quite understand its benefits or how to properly use it yet. In this tutorial we'll get you all caught up so you can finally start taking advantage of requestAnimationFrame() to perform animations more optimally in your scripts. Let's roll!

Why we need another hero- requestAnimationFrame

First of all, lets talk about requestAnimationFrame() as an idea and why we even need such a method. Traditionally to create an animation in JavaScript, we relied on setTimeout() called recursively or setInterval() to repeatedly execute some code to make changes to an element frame by frame, such as once every 50 milliseconds:

var adiv = document.getElementById('mydiv')
var leftpos = 0
setInterval(function(){
	leftpos += 5
	adiv.style.left = leftpos + 'px' // move div by 5 pixels each time
}, 50) // run code every 50 milliseconds

While the above code is logically sound, its actual execution is far from perfect. The problem with using setTmeout/setInterval for executing code that changes something on the screen is twofold.

  • What we specify as the delay (ie: 50 milliseconds) inside these functions are often times not honoured due to changes in user system resources at the time, leading to inconsistent delay intervals between animation frames.

  • Even worse, using setTimeout() or setInterval() to continuously make changes to the user's screen often induces "layout thrashing", the browser version of cardiac arrest where it is forced to perform unnecessary reflows of the page before the user's screen is physically able to display the changes. This is bad -very bad- due to the taxing nature of page reflows, especially on mobile devices where the problem is most apparent, with janky page loads and battery drains. An iPhone or two have even caught fire as a result (just a joke Apple, no law suits please)!

requestAnimationFrame() to the rescue

It is for the above reasons requestAnimationFrame() was introduced. The method in a nutshell allows you to execute code on the next available screen repaint, taking the guess work out of getting in sync with the user's browser and hardware readiness to make changes to the screen. When we call requestAnimationFrame() repeatedly to create an animation, we are assured that our animation code is called when the user's computer is actually ready to make changes to the screen each time, resulting in a smoother, more efficient animation. Furthermore, code called via requestAnimationFrame() and running inside background tabs in your browser are either paused or slowed down significantly (to 2 frames per second or less) automatically to further save user system resources- there's no point in running an animation that isn't being seen is there?

So requestAnimationFrame() should be used in place of setTimeout/ setInterval for animations, but how exactly do we go about doing that? Before we get to that, lets look at browser support first. requestAnimationFrame() today enjoys wide adoption amongst modern browsers- IE10+, FF11+, Chrome, and Safari etc. For some older versions of these browsers, a vendor prefix is needed in front of the method name to work. Even on browsers that don't support this method in any incarnation, we can simply fallback in those cases to setTimeout() instead to call the code after a certain delay instead. The following code creates a universal rrequestAnimationFrame() and its counterpart cancelAnimationFrame() function that works in the maximum number of browsers, with a fallback built in:

window.requestAnimationFrame = window.requestAnimationFrame
	|| window.mozRequestAnimationFrame
	|| window.webkitRequestAnimationFrame
	|| window.msRequestAnimationFrame
	|| function(f){return setTimeout(f, 1000/60)} // simulate calling code 60 

window.cancelAnimationFrame = window.cancelAnimationFrame
	|| window.mozCancelAnimationFrame
	|| function(requestID){clearTimeout(requestID)} //fall back

For the fallback setTimeout() code, notice the delay being set at 1000/60, or around 16.7 milliseconds. This value simulates how often the real requestAnimationFrame() will typically be called by the browser each time it's invoked based on the typical user's screen refresh rate of 60 frames per second.

Understanding and using requestAnimationFrame()

The syntax for requestAnimationFrame is very straightforward:

requestAnimationFrame(callback)

We enter a callback function containing the code we wish to run, and requestAnimationFrame() will run it when the screen is ready to accept the next screen repaint. Some noteworthy details:

  • The callback function is automatically passed a timestamp indicating the precise time requestAnimationFrame() was called.

  • requestAnimationFrame() returns a non 0 integer that can be passed into its nemesis counterpart cancelAnimationFrame() to cancel a requestAnimationFrame() call

Here's an example of calling requestAnimationFrame() once to move a DIV just 5 pixels from its original location on the screen:

var adiv = document.getElementById('mydiv')
var leftpos = 0
requestAnimationFrame(function(timestamp){
	leftpos += 5
	adiv.style.left = leftpos + 'px'
})

The above code is very similar to using setTimeout() to run the same code, except instead of after the user defined delay, the code is called on the next available screen repaint, typically around 16.7 milliseconds based on a typical screen refresh rate of 60fps. The exact number may fluctuate and frankly doesn't matter; what's important to realize is that the browser will now intelligently invoke our code only when it is ready to accept changes to the screen, not before.

Calling requestAnimationFrame() once is pretty meaningless most of the time. The magic happens when we call it "recursively" to construct the desired animation frame by frame, with each frame being called only when the browser is ready for it. This this how requestAnimationFrame() becomes superior to setTimeout or setInterval when it comes to handling animation related code efficiently. Lets rewrite our initial example of moving a DIV across the screen 5 pixels at a time using requestAnimationFrame():

var adiv = document.getElementById('mydiv')
var leftpos = 0
function movediv(timestamp){
	leftpos += 5
	adiv.style.left = leftpos + 'px'
	requestAnimationFrame(movediv) // call requestAnimationFrame again to animate next frame
}
requestAnimationFrame(movediv) // call requestAnimationFrame and pass into it animation function

The above code shows the basic blueprint for using requestAnimationFrame() to create an animation, by defining your animation code inside a function, then inside this function calling itself recursively through requestAnimationFrame() to produce each frame of our animation. To kick start the animation, we make a call to requestAnimationFrame() outside the animation function with that function as the parameter.

Animation over time in requestAnimationFrame()

So it's simple enough to repeatedly call an animation function using requestAnimationFrame(), but most animations are much more finicky, having to stop at some point after a certain objective has been achieved over a certain amount of time. Take our example of moving the DIV above; in a real life scenario, what we probably want to do is move the DIV 400 pixels to the right over a time of say 2 seconds. To do this with requestAnimationFrame(), we can take advantage of the timestamp parameter that's passed into the callback function. Lets see how this works now, by retooling our DIV moving code above so it moves the DIV a certain distance over a certain amount of time:

var adiv = document.getElementById('mydiv')
var starttime

function moveit(timestamp, el, dist, duration){
	//if browser doesn't support requestAnimationFrame, generate our own timestamp using Date:
	var timestamp = timestamp || new Date().getTime()
	var runtime = timestamp - starttime
	var progress = runtime / duration
	progress = Math.min(progress, 1)
	el.style.left = (dist * progress).toFixed(2) + 'px'
	if (runtime < duration){ // if duration not met yet
		requestAnimationFrame(function(timestamp){ // call requestAnimationFrame again with parameters
			moveit(timestamp, el, dist, duration)
		})
	}
}

requestAnimationFrame(function(timestamp){
	starttime = timestamp || new Date().getTime() //if browser doesn't support requestAnimationFrame, generate our own timestamp using Date
	moveit(timestamp, adiv, 400, 2000) // 400px over 1 second
})

Demo:

Lets go over how this works now.

  • Just before the animation runs, we set the startime variable to the current time using either requestAnimationFrame's timestamp parameter, or if requestAnimationFrame isn't supported, a less precise new Date().getTime() instead. The former is a value automatically passed in as the first parameter of the callback function of requestAnimationFrame that contains a highly accurate representation of the current time in milliseconds (accurate to 5 microseconds). This lets us know when the animation started running.

  • Inside the animation function moveit(), we capture the current time of the current "frame" using variable timestamp. We use the difference between that and the animation starttime to figure out at what "point" along the animation we're currently at, and change the DIV's position accordingly out of the total distance (ie: 400px).

Slowing down or cancelling requestAnimationFrame()

The standard requestAnimationFrame runs at around 60fps under ideal conditions (or once every 16.7ms), in sync with the refresh rate of the typical monitor. If your animation requires a different frames per second (up to 60 fps) or simply doesn't require that high a level of refresh rate, you can slow it down by calling requestAnimationFrame inside setTimeout(). That way, you get the desired frame rate while reaping the benefits of requestAnimationFrame:

var adiv = document.getElementById('mydiv')
var leftpos = 0
var fps = 20
function movediv(timestamp){
	setTimeout(function(){ //throttle requestAnimationFrame to 20fps
		leftpos += 5
		adiv.style.left = leftpos + 'px'
		requestAnimationFrame(movediv)
	}, 1000/fps)
}

requestAnimationFrame(movediv)

In this version of moving a DIV horizontally, we're throttling the frames per second to roughly 20, by calling requestAnimationFrame inside setTimeout() each time.

- Cancelling requestAnimationFrame()

Just like with setTimeout/ setInterval, you can cancel a requestAnimationFrame call, and in identical fashion as well. requestAnimationFrame when called returns a non 0 integer that can be captured inside a variable and passed into its nemesis counterpart cancelAnimationFrame() to stop it from being invoked again. The following logs the timestamp parameter value of requestAnimationFrame for two seconds,  using cancelAnimationFrame to stop the former:

var reqanimationreference

function logtimestamp(timestamp){
	console.log(timestamp)
	reqanimationreference = requestAnimationFrame(logtimestamp)
}

requestAnimationFrame(logtimestamp)

setTimeout(function(){ // cancel requestAnimationFrame after 2 seconds
	cancelAnimationFrame(reqanimationreference)
}, 2000)

Here is a slightly more elaborate example that continuously changes a DIV's width using requestAnimationFrame when the mouse enters the parent container (onmouseenter), and cancels it onmouseleave:

Demo:

Move mouse over battery

View Source Code

Conclusion

As you can see, requestAnimationFrame() is actually very simple in concept and execution, once you understand its purpose and common patterns. Expect this method to show up wherever JavaScript powered animations do; for the frameworks addicts, jQuery 3.0 apparently will be utilizing requestAnimationFrame() internally for all animation related functions moving forward. However we don't have to wait until that day to start creating smoother, more efficient and mobile friendly animations today!

End of Tutorial

Partners