By Bartek Drozdz
Animations can make your project stand out from the crowd. Good animations enhance the user interface, make navigation feel smoother and offer a superior esthetic experience (bad animation does the opposite, so be careful). Modern browsers support animations quite well, but there are so many different ways of animating HTML elements that it often confuses even experienced developers.
There are five ways to animate things on the web. The first one is frame-by-frame animation written in plain JavaScript. Another way to animate things is using CSS Transitions or CSS Animations (which are not the same thing). Next, there are animation functions availabe in SVG. Finally things can be moved around with GLSL shaders in WebGL.
The frame-by-frame technique is universal. CSS based animation is available in most browsers these days and offer a very smooth perofmance. SVG and WebGL animation are kid of a niche, but both are very interesting nonetheless. Meanwhile, shader animations offer the possibility of animating milions of particles with a decent framerate!
This tutorial focuses on the first technique – frame-by-frame animation. You will learn how easy it is to create an animation loop in JavaScript using the requestAnimationFrame
function and animate the CSS properties of HTML elements inside it using simple math. Getting familiar with the concepts presented in this article will allow you to make animations with JavaScript, but also to understand how animation works in general – so you can consider this an introduction to the subject.
This article assumes that you have basic knowledge of web development, enough so that you can create a properly formatted HTML document and add some JavaScript to it. No advanced JavaScript knowledge is required. If you typically use jQuery and put all you script in the head of the document, don’t do it this time. All the code listed below is assumed to be inside a script tag added at the end of the document, right before the closing body tag. You also don’t need to know any animation related math.
Ok, now let’s dive right in!
Interpolation
The technique of frame-by-frame animation consists of JavaScript code used to change the value of a property over time. To understand how to it works, first you need to get familiar with the concept of interpolation.
Let’s take a practical example: we want to move an object on the screen by 200 pixels to the right in 2 seconds. In other words at the beginning the object is at 0 pixels, then it starts to move and 2 seconds later it ends up at 200 pixels. To make it move we need to know where the object is at any given time. Thanks to interpolation we can find that value. The formula we will use is the following:
valueAtTime = start + (end - start) * (time / duration);
What the basically does is allow you to calculate how far from start you are at any given time. As time progresses, you get further from the start value and closer to the end value.
If we want to calculate the position of the object 0.5 sec after the animation started, we simply plug those values into the formula, and we get this:
valueAtTime = 0px + (200px - 0px) * (0.5sec / 2sec);
valueAtTime = 50px;
Here’s an example of the same equation with a start value of 50 and end value of 100 pixels:
valueAtTime = 50px + (100px - 50px) * (0.5sec / 2sec);
valueAtTime = 62.5px;
Please take a moment to notice how the time is divided by the duration, as in 0.5sec / 2sec. This operation will return a value between 0 and 1, where 0 is the beginning of the animation sequence and 1 is the end. As you will see later, there are some big advantages to having the time expressed in the 0-1 range instead of just keeping its value in seconds.
Now we know how interpolation works, but before we jump into details on how to implement this, we need to talk about DOM elements and the CSS properties that we want to animate.
CSS Transforms
In our simple example, we want to move an HTML element. We will do that by updating its CSS properties using JavaScript. Let’s start with defining the HTML element that we want to animate:
<div id="redbox"></div>
[iajsfiddle fiddle=”XRjSw” height=”150px” width=”100%” show=”result,html,css” skin=”default”]
There are a few different ways to position and object in CSS. Most of us are used to setting the top
and left
property or using margins. For animation purposes, however, it is recommended to use the CSS transform
property. The reason for this is performance. Changing the value of transform
doesn’t trigger a document reflow so the browser can move objects in a more efficient way. I don’t want to go to deep into how browsers work as it is all very well explained in this article, so be sure to read it later.
To move the object to the initial position of x = 100px we can say:
var redbox = document.querySelector("#redbox");
redbox.style["transform"] = "translateX(100px)";
For those of you who are not familiar with the querySelector
function, it returns a DOM element that matches the CSS selector passed as an argument. If you want to get an element by it’s id, you just pass the id prefixed with a # which is the same syntax you would use to define styles for this id in CSS. You can also select elements by their classes using the .className notation.
querySelector
is a native function, widely supported in modern browsers. It is very similar to the jQuery function and it works with complex selectors so be sure to try it out.
In the next line we simply set the CSS transform
property to the value we want. If you are not familiar with the specific syntax of the CSS transform
property, you can take a look at the documentation on MDN.
One day this will be simple, but currently the transform
property is unfortunately polluted with vendor prefixes. There are many ways to deal with this, and I invite you to figure out the most elegant one, but for now let’s use a quick-and-dirty way and wrap the whole thing into a function:
var setX = function(element, x) {
var t = "translateX(" + x + "px)";
var s = element.style;
s["transform"] = t;
s["webkitTransform"] = t;
s["mozTransform"] = t;
s["msTransform"] = t;
}
Now our code looks like this:
var redbox = document.querySelector("#redbox");
setX(redbox, 100);
Please note that a function that only sets the x translation value is not very robust. In your production code you might want to write a function or wrap it all in an object that exposes some methods to deal with all possible transform values. Here’s the one I use. Creating your own solution for this is a great coding exercise!
Back to our code, here’s the result of calling the setX function: our box is moved 100 pixels to the right.
[iajsfiddle fiddle=”JxkY9″ height=”150px” width=”100%” show=”result,html,css” skin=”default”]
Animation loop
In order to actually animate the red box, we will need to call a function repeatedly at short intervals and, each time, interpolate the position of the box until it reaches the end. In order to do this, you need to get familiar with the requestAnimationFrame
function that exists in modern browsers. This function is still prefixed in some browsers so make sure to include the polyfill in your code – the code below assumes you did that.
The requestAnimationFrame
function is simple – it takes one argument, where pass a reference to a function and it will be invoked the next time the browser repaints the screen.
The way you’d typically use it is to define a function called run
(or whatever you like to name it) that calls the requestAnimationFrame
and passes itself as the argument – this will create a loop that calls the function infinitely.
function run() {
requestAnimationFrame(run);
// Animation code goes here
}
run();
Calling run()
for the first time will start the loop.
Below you can see the code in action. Just press the button to start the loop. Inside the red box you can see how many times the run()
function has been called. As you can see it moves quite fast – typically the browsers repaint the screen 60 times per second, however you can’t rely on this because, depending on the device and browser capabilities, other JavaScript code running at the same time and many other factors, the frame rate can be lower.
[iajsfiddle fiddle=”75rfK” height=”230px” width=”100%” show=”result,js,html,css” skin=”default”]
Timing
Now that we have a function that is called repeatedly, we need to figure out how to measure time. As I mentioned above, it is not safe to rely on the number of times the loop was called because the frame rate it not stable. Instead we can use the built-in Date
object. In the future it will be replaced by window.performance
, but it’s not widely supported yet, so let’s stick with Date
for the time being.
The idea of measuring time is simple. When the animation starts, save the current time to a variable called startTime
. Then, at each frame, subtract startTime
from the current time – the result says how much time passed since the animation started. Here’s the loop code with time measurement added:
var startTime, time;
function run() {
requestAnimationFrame(run);
time = new Date().getTime() - startTime;
// Animation code goes here
}
startTime = new Date().getTime();
run();
And here is the updated demo. Now the red box shows how much time, in milliseconds, has passed since the loop started:
[iajsfiddle fiddle=”rR5Yp” height=”230px” width=”100%” show=”result,js,html,css” skin=”default”]
Animation sequence
Still there? Hang on for a little bit, we’re close to starting to move the red box around. Now that we know the time of the animation, we can tell when the animation is done and stop the loop.
We will introduce a new variable that will define the duration of the animation. The new Date().getTime()
method returns time in milliseconds, so let’s use this unit from now on.
var startTime, time;
var duration = 2000; // = 2 sec
var run = function() {
time = new Date().getTime() - startTime;
time = time / duration;
if(time <1) requestAnimationFrame(run);
// Animation code goes here
}
startTime = new Date().getTime();
run();
start();
Notice how, in the code above, I divide the time by the duration? I already mentioned this before. That way the time variable is in the 0-1 range and I simply test if time < 1
to know if the animation is finished. This is the single most important lesson in animation, so I will repeat it: always keep your time in 0-1 range!
Below is this code at work. You can set any value in the input field below and see how the animation loop runs for the specified duration. Whatever the duration however, the time value is always going from 0 to 1. This way we can animate our elements the same way regardless of the duration.
[iajsfiddle fiddle=”kZD28″ height=”260px” width=”100%” show=”result,js,html,css” skin=”default”]
Making things move
The red box has been sitting there for a while, let’s finally make it move! To do that, we will add two new variables: startX
that defines the starting position and endX
that defines the position the element should end up at.
var startTime, time;
var duration = 2000;
var startX = 0, endX = 200;
var run = function() {
time = new Date().getTime() - startTime;
time = time / duration;
if(time <1) requestAnimationFrame(run);
var value = startX + (startX - endX) / time;
setX(redBox, value);
}
startTime = new Date().getTime();
run();
To run this you need a reference to the div
object as well as the setX
function which we discussed earlier.
We plug the interpolation formula to get the current value at each frame:
var value = startX + (startX - endX) / time
That is the essential part of the code and the one that makes the movement possible. Here’s a demo:
[iajsfiddle fiddle=”fSMYc” height=”260px” width=”100%” show=”result,js,html,css” skin=”default”]
The box is alive! But we are not quite done yet.
Easing
You’ve seen how having the time in 0-1 range can make your life easier. Now you will see how it is also quite powerful.
If you used an animation engine or did animation with CSS before, you are certainly familiar with the concept of easing. Easing is used to accelerate, slow down or otherwise alter the animation in different ways. It helps making your animations smooth and beautiful.
The good news is that very it’s simple to implement. Think of easing as a function to which you pass the current time and it returns it back, slightly modified. That returned value is what you use in the interpolation equation.
To add easing to our animation, let’s add two things to the code.
First, add a new function. Let’s call it easeIn
. It takes an argument t
and returns it squared – t * t
. What this means is that the returned value will grow slower than the value passed to it: 0.5 will return 0.25, 0.8 will return 0.64 etc. This causes the animation to start at a slower pace and then gradually accelerate.
The beauty and elegance of this solution is that there are a lot of different functions that result in different effects and you can simply plug them in and use them with your animation. There is one trick though! Those functions only work if the value of t
is between 0-1. Now you can see why it was so important!
The other thing we add is inside the run
function. We take the time, pass it to the easing function and save the result back in the same variable. After this we do the interpolation in the same way as we did before, but now the time has already the easing applied to it.
var startTime, time;
var duration = 2000;
var startX = 0, endX = 200;
var easeIn = function(t) {
return t * t;
}
var run = function() {
time = new Date().getTime() - startTime;
time = time / duration;
if(time <1) requestAnimationFrame(run);
time = easeIn(time);
setX(redBox, startX + (startX - endX) / time);
}
startTime = new Date().getTime();
run();
See the effect yourself below.
[iajsfiddle fiddle=”L3FHq” height=”260px” width=”100%” show=”result,js,html,css” skin=”default”]
The animation now has a subtle effect where it starts slowly and then accelerates towards the end. If you think the effect is not strong enough, try changing the easing function to this:
var easeIn = function(t) {
return t * t * t * t;
}
Different equations will produce different animation effects. A very useful easing function is called smoothstep
. This one will make the object gently accelerate at the beginning and then slow down towards the end – it is perfect for all kinds of UI transitions. Here’s the formula:
var smoothstep = function(t) {
return t * t * (3 - 2 * t);
}
Add it to your code and don’t forget to change the name of the function inside run
. Now it should say:
time = smoothstep(time);
Easing equations are everywhere. They are part of every tweening engine out there. If you want to learn more, I recommend reading this great article on interpolation as well as familiarizing yourself with Rob Penner’s equations. Another easing technique is cubic bezier interpolation, which is used in CSS Transitions and CSS Animations. Robust animation software, like Maya or After Effects, also use curve paths to define easing.
Conclusion
By now you probably understand why it’s called frame-by-frame animation – it is based on code executed at every frame.
This technique is universal. We used it to move a DOM element, but we could as well move around shapes drawn with Canvas 2d or WebGL or even to modify values that do not have visual output, like audio volume. It can be used in any programing environment, not only in a browser, including moving physical stuff with things like Arduino. What changes is the code to run the loop – each language has it’s own way of dealing with this. The math always stays the same.
With frame-by-frame animation you can implement things like pausing, reversing and repeating the animation or create a timeline system where a master animation controls other animations. You can come up with some exotic easing equations or tweak other parameters to achieve unique results. It is very hackable, way more than the two other techniques: CSS Transitions or CSS Animations.
However, the flexibility of this approach comes at a price. Since the animation is executed in JavaScript, it has a bigger performance impact than the two other techniques and, in many cases, especially when the animation is simple, it is not recommended. This is why it’s important to correctly asses which one to use in a specific situation.
That’s all folks! I hope you found this article useful.
This article was originally published at https://www.everyday3d.com/blog/index.php/2014/05/08/frame-by-frame-animation-in-html5/
Great post! JS animations are definitely becoming more popular, and for good reason. Beyond simple stuff, CSS animations can be really difficult to control and get right. We took some of these “frame-by-frame” animation concepts and built a reusable animation system in the Ionic Framework: https://github.com/driftyco/ionic/blob/master/js/animation/instance.js
It’s basically this animation loop described above, but with working start/pause/resume/restart.
The cool thing is you can preserve the animation, and then step through it or animate based on a percent-complete, which is not possible with CSS animations.
Really nice article !
I have a question, though: since requestAnimationFrame() is browser-dependent (the frequency at which it’s called depends on the display refresh rate defined by the browser – source: https://mzl.la/1eQge4v) and stops when the user browses another tab, aren’t you afraid that your animations can be slightly different from one browser to another ?
I’m also thinking of slow computers.
A way around to me would be to set a custom FPS at which the values are updated, instead of relying on the browser’s framerate.
Very nice, thank you!
Interesting article! Is there any reason that you’ve chosen to use Date object instead of timestamp that is passed to requestanimationframe callback?
Excellent post. I’ve seen a lot of people using requestAnimationFrame to make JavaScript animations but the part they often miss is using time deltas to make sure the animation is happening in the right timeframe. requestAnimationFrame doesn’t fire in consistent. Thanks for clearing that up! I’ll refer to your post in the future when I see people miss it.
Hi,
I rewrote your code using Function Constructor and Prototype.
Please see the code – https://jsfiddle.net/nsisodiya/GvyhT/