- The basics of the animation
- Animation refactored
- Maths, the function of progress
delta
- Reverse functions (easeIn, easeOut, easeInOut)
- Changing
step
- CSS transitions
- Optimization hints
- Summary
Usually, a framework handles the animation for you.
However, you may wonder how the animation is implemented in pure JavaScript, and what are the possible problems.
Understanding the technique is also essential to create complex animations. Even with the help of frameworks.
The basics of the animation
The JavaScript animation is implemented as gradual changing of DOM element styles or canvas objects.
The whole process is split into pieces, and each piece is called by timer. Because the timer interval is very small, the animation looks continuous.
The pseudocode is:
var id = setInterval(function() { /* show the current frame */ if (/* finished */) clearInterval(id) }, 10)
The delay between frames is 10 ms
here, which means 100 frames per second.
In most JavaScript frameworks it is 10-15 ms by default. Less delay makes the animation look smoother, but only if browser is fast enough to animate every step in time.
If the animation requires many calculations, CPU may get 100% load, and things become sluggish. In this case the delay can be increased. For example, 40ms gives 25 frames per second, close to the cinema standard which is 24.
We’re using setInterval
, not recursive setTimeout
, because we want a frame once per interval, not *a fixed delay between frames.
See Understanding timers: setTimeout and setInterval for detail differences between setInterval
and recursive setTimeout
.
Example
For example, the element is visually moved by changing element.style.left
from 0 to 100px. The change is done by 1px every 10ms.
<!DOCTYPE HTML> <html> <head> <link type="text/css" rel="stylesheet" href="/files/tutorial/browser/animation/animate.css"> <script> function move(elem) { var left = 0 function frame() { left++ // update parameters elem.style.left = left + 'px' // show frame if (left == 100) // check finish condition clearInterval(id) } var id = setInterval(frame, 10) // draw every 10ms } </script> </head> <body> <div onclick="move(this.children[0])" class="example_path"> <div class="example_block"></div> </div> </body> </html>Open the code in new window
Click to animate:
Animation refactored
To make the animation generic we introduce the following parameters:
delay
- Time between frames (in ms, 1/1000 of second). For example, 10ms
duration
- The full time the animation should take, in ms. For example, 1000ms
Then on animation starts, we also use:
start
- The time of animation start,
start = new Date
.
As the core of the animation process, on each frame we calculate:
timePassed
- The time (in ms) passed from the animation start.
Changes from 0 to
duration
, but may occasionally exceedduration
because the browser timer is imprecise.
progress
- The fraction of animation time that has already passed, calculated on every frame as
timePassed/duration
. Gradually moves from 0 to 1.For example, value
progress = 0.5
means that half ofduration
time is out.
delta(progress)
- A function, which returns the current animation progress.
For example, we are animating the
height
property from 0% to 100%.We could do it uniformly, so the
progress
maps toheight
linearly:Mapping: progress = 0
->height = 0%
progress = 0.2
->height = 20%
progress = 0.5
->height = 50%
progress = 0.8
->height = 80%
progress = 1
->height = 100%
But we may also want the animation to start slowly and then speed up, so that at half of time the height will be only 25%, and then faster and faster, up to 100%.
Mapping: progress = 0
->height = 0%
progress = 0.2
->height = 4%
progress = 0.5
->height = 25%
progress = 0.8
->height = 64%
progress = 1
->height = 100%
delta(progress)
is the function which maps time progress"progress"
to animation progress"delta"
.The animation progress is not a
height
yet, but a number, usually from 0 to 1.Further in the article, we consider several types of
delta
with examples.
step(delta)
- The function, which actually does the job.
It takes the result of
delta
and applies it.For the height example, there would be:
function step(delta) { elem.style.height = 100*delta + '%' }
So to sum up the main parameters:
- The
delay
is thesetInterval
second param. - The
duration
is how long the animation is going to take. - The
progress
is how much time has passed, divided by duration to make it move from 0 to 1. - The
delta
calculates current progress of animation, given current time. - The
step
actually does the job. It takes current animation progress and applies it the element.
The generic animation
Let’s put the parameters discussed above into a short, flexible animation core.
The function animate
below does time management and leaves the work to delta
and step
:
function animate(opts) { var start = new Date var id = setInterval(function() { var timePassed = new Date - start var progress = timePassed / opts.duration if (progress > 1) progress = 1 var delta = opts.delta(progress) opts.step(delta) if (progress == 1) { clearInterval(id) } }, opts.delay || 10) }
The object opts
should contain animation options:
delay
duration
- function
delta
- function
step
The algorithm exactly follows the description.
Example
Let’s create a movement animation on it’s base:
function move(element, delta, duration) { var to = 500 animate({ delay: 10, duration: duration || 1000, // 1 sec by default delta: delta, step: function(delta) { element.style.left = to*delta + "px" } }) }
It delegates the work to animate
, giving it delay
, user-provided duration, delta
, and the step
.
delta = function(p) {return p}
- Means that the animation progress is uniform over time.
step
- Uses a simple formula to map
0..1
, returned bydelta
to interval0..to
. Applies the result to theelement.style.left
.
Usage:
<div onclick="move(this.children[0], function(p) {return p})" class="example_path"> <div class="example_block"></div> </div>
Click to animate:
Maths, the function of progress delta
The animation is a change of property over time, following the given law. In JavaScript animation, the law is implemented as delta
function.
Different deltas
make animation speed, acceleration and other parameters behave is a sheer variety of ways.
Mathematical formulas are usually used here. They may seem unfamiliar to people who only do web-programming and forgot school maths. But in this section we’ll browse most popular formulas and see how they work.
The examples animate movement, providing different delta
.
Linear delta
function linear(progress) { return progress }
Graph:
Horizontal line is progress
, vertical line is delta(progress)
.
We’ve seen it already. The linear delta makes animation proceed at fixed pace.
Example:
<div onclick="move(this.children[0], linear)" class="example_path"> <div class="example_block"></div> </div>
Click to animate:
Power of n
Also a simple case. The delta
is progress
in n-th
degree. Particular cases are quadrantic, cubic function etc.
For the quadrantic function:
function quad(progress) { return Math.pow(progress, 2) }
Graph for the quadrantic function:
Example for the quadrantic function (click to animate):
Increasing the power affects the acceleration. For example, the graph for 5-th degree:
And the example (click to animate):
Circ: a piece of circle
The function:
function circ(progress) { return 1 - Math.sin(Math.acos(progress)) }
Graph:
Example (click to animate):
Back: the bow function
This function works as the bow: first we “push bak the string” and then “shoot”.
Unlike the previous functions, it depends on an additional parameter x
, which is the “coefficient of elasticity”. It defines the distance of “pushing back”.
The code is
function back(progress, x) { return Math.pow(progress, 2) * ((x + 1) * progress - x) }
Graph for x = 1.5
:
Example for x = 1.5
(click to animate):
Bounce
Imagine we release the ball, it falls on the floor, then bounces a few times and stops.
The bounce
function does exactly the reverse thing. The property will “bounce” until it reaches the target point.
The function is a bit more complicated than previous ones, and has no easy mathematical formula.
function bounce(progress) { for(var a = 0, b = 1, result; 1; a += b, b /= 2) { if (progress >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * progress) / 4, 2) + Math.pow(b, 2) } } }
*The code is taken from MooTools.FX.Transitions. As far as I know, there are other implementations of bounce
too.
Example (click to animate):
Elastic
The function also depends on additional parameter x
, which defines the initial range.
function elastic(progress, x) { return Math.pow(2, 10 * (progress-1)) * Math.cos(20*Math.PI*x/3*progress) }
The Graph for x=1.5
:
Example for x=1.5
(click to animate):
In this example, the time is 2 seconds, to make the animation smoother.
Reverse functions (easeIn, easeOut, easeInOut)
A JavaScript framework usually provides a pack of delta
functions.
Their direct use is called “easeIn”.
Sometimes, it is required to show the animation in the time-reversed mode. This is called “easeOut” and implemented by “time-reversing” delta
.
easeOut
In the “easeOut” mode, the value of delta is transformed as
deltaEaseOut = 1 - delta(1 - progress)
For example, here’s the Bounce
in “easeOut” mode:
function bounce(progress) { for(var a = 0, b = 1, result; 1; a += b, b /= 2) { if (progress >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * progress) / 4, 2) + Math.pow(b, 2); } } } function makeEaseOut(delta) { return function(progress) { return 1 - delta(1 - progress) } } var bounceEaseOut = makeEaseOut(bounce)
Click to animate:
Let’s see how it the easeOut
changes the behavior:
The red is easeIn (normal), the green is easeOut (time-reversed).
- Normally it bounces at bottom slowly first, and reaches the top at finish.
- After easeOut it bounces to the top at once, and then bounces at the bottom slowly.
The easeOut
makes the effect time-reversed.
easeInOut
Another option is to show the delta
effect at both start and end of the animation. This is called “easeInOut”.
The transform looks this way:
if (progress <= 0.5) { // the first half of the animation) return delta(2 * progress) / 2 } else { // the second half return (2 - delta(2 * (1 - progress))) / 2 }
For example, easeInOut
for bounce
(click to animate):
This example has duration of 3 sec to give time for both starting and ending effects.
The code which transforms the delta
:
function makeEaseInOut(delta) { return function(progress) { if (progress < .5) return delta(2*progress) / 2 else return (2 - delta(2*(1-progress))) / 2 } } bounceEaseInOut = makeEaseInOut(bounce)
The transform merges two graphs: easeIn
and easeOut
into one.
For example, let’s see the effect of easeOut
/easeInOut
on function circ
:
easeInOut
scales easeIn
to the first half of the animation and easeOut
to the second half.
As the result, the animation starts and ends with same effect.
The plotter
I prepared a graph plotter to see various deltas in action, both normal and transformed.
Choose the function, then press Draw!
Add easings by checkboxes.
- easeIn - the basic behavior: slow at first then accelerating.
- easeOut - the time-reversed behavior. Fast at start, then slower and slower.
- easeInOut - the two behaviors merged. The animation splits into two halves. First half is
easeIn
, theneaseOut
.
Try “bounce”, for example. Should provide some enlightment.
The process of animation is totally at your command through delta
. You can make it as close natural as you want.
And yeah. If you ever studied maths… See, it helps here, after all
To be realistical, standard deltas
cover about 95% of the needs.
Changing step
Anything can be animated. Instead of a movement in the examples above, you can change opacity, width, height, color… Anything you can think of!
Color highlight
The function highlight
given below animates color change.
function highlight(elem) { var from = [255,0,0], to = [255,255,255] animate({ delay: 10, duration: 1000, delta: linear, step: function(delta) { elem.style.backgroundColor = 'rgb(' + Math.max(Math.min(parseInt((delta * (to[0]-from[0])) + from[0], 10), 255), 0) + ',' + Math.max(Math.min(parseInt((delta * (to[1]-from[1])) + from[1], 10), 255), 0) + ',' + Math.max(Math.min(parseInt((delta * (to[2]-from[2])) + from[2], 10), 255), 0) + ')' } }) }
And now the same, but delta = makeEaseOut(bounce)
:
Text typing
You can create tricky animations, like text typing in bounce mode:
The source:
function animateText(textArea) { var text = textArea.value var to = text.length, from = 0 animate({ delay: 20, duration: 5000, delta: bounce, step: function(delta) { var result = (to-from) * delta + from textArea.value = text.substr(0, Math.ceil(result)) } }) }
Make the ball bounce Click on the ball to see the result.
Write the code to animate it.
Start from the source document: tutorial/browser/animation/ball-bounce-src/index.html.
The ball, animate
and a pack of delta
functions from the article are attached.
In HTML/CSS, the falling of the ball is changing it’s elem.style.top
from initial 0 to bottom.
The largest top is field.clientHeight - ball.clientHeight
, so that the top of the ball is exactly /ball height/ px over the bottom.
The function to use is, of course, bounce
, with effect at the end.
Gives us the following pretty picture:
var img = document.getElementById('ball') var field = document.getElementById('field') img.onclick = function() { var from = 0 var to = field.clientHeight - img.clientHeight animate({ delay: 20, duration: 1000, *!* delta: makeEaseOut(bounce), step: function(delta) { img.style.top = to*delta + 'px' } */!* }) }
The full solution is at tutorial/browser/animation/ball-bounce/index.html.
Make the ball bounce right. Click on the ball to see the result.
Write the code to animate it. The distance to the right is 100px
.
Start from the source document: tutorial/browser/animation/ball-bounce-src/index.html.
The ball, animate
and a pack of delta
functions from the article are attached.
See the task Animate the ball to create a bouncing ball.
Here we need to add one more animation for elem.style.left
.
Unlike top
, the horizontal coordinate does not bounce. There should be another law.
We could apply linear
, but horizontal movement should become a bit slower as the ball bounces right, so something like makeEaseOut(quad)
is closer to reality.
The code:
img.onclick = function() { var height = document.getElementById('field').clientHeight - img.clientHeight var width = 100 animate({ delay: 20, duration: 1000, delta: makeEaseOut(bounce), step: function(delta) { img.style.top = height*delta + 'px' } }) *!* animate({ delay: 20, duration: 1000, delta: makeEaseOut(quad), step: function(delta) { img.style.left = width*delta + "px" } }) */!* }
The full solution is at tutorial/browser/animation/ball-bounce-right/index.html.
CSS transitions
All modern browsers except IE support CSS transitions - a way to setup simple animations with CSS.
The idea is simple. If you want to animate a CSS property change - set duration
with special CSS. The browser handles animation on it’s own.
For example, the CSS below makes color
animating for 2s.
.animated { transition-property: background-color; transition-duration: 2s; }
Any background color change will be animated for 2 seconds. There is also a shorthand
"transition"
property.
Example
The example below works in all modern browsers except IE. Also, no FF<4.
<style> .animated { transition: background-color 2s; -webkit-transition: background-color 2s; -o-transition: background-color 2s; -moz-transition: 2s; } </style> <div class="animated" onclick="this.style.backgroundColor='red'"> <span style="font-size:150%">Click to animate (no IE, no FF<4)</span> </div>
Because CSS transitions are still a draft, browsers use them in prefixed form.
Actually, there is no browser which supports bare "transition"
without a prefix.
Limitations
CSS transitions are very limited comparing to JavaScript.
The limitations are:
- We can set
delta
, calledtransition-timing-function
, but CSS allows only cubic bezier curves here. And no values out of [0,1] interval. - Only CSS properties can be animated. But the good thing, multiple properties can animate at once.
Well, of course CSS is less powerful than JavaScript. But simple things should be done simply, right? So obviously, CSS animations can be useful.
Pitifully, no support from IE and draft stage of the standard is the reason of slow adoption of CSS transitions.
Optimization hints
- Many timers cause CPU consumption.
- If you want to run many animations at once, for example to show a bunch of falling snowflakes, manage them in a single timer.
Each timer causes a redraw. The browser is much more effective if there is a single redraw for all animations.
An animation framework usually has a single
setInterval
, and runs all frames on it’s tick.
- Help the browser to render
- The browser manages rendering tree, and elements depend on each other.
If the animated element is deep in the DOM, then other elements depend on it’s geometry and position. Even if the animation actually doesn’t shift them, the browser has to perform additional calculations.
To make the animation consume less CPU (and be smoother), don’t animate the element deep in DOM.
Instead:
- Remove the animated element from DOM and attach it directly to
BODY
at the start. You’ll probably need to make itposition: absolute
and setup coordinates. - Animate it.
- Put it back into the DOM.
The trick can fix shaky animation and save CPU.
- Remove the animated element from DOM and attach it directly to
On phones and pads, consider replacing JavaScript animation with CSS transitions, if it is running too slow.
Summary
The animation is done using setInterval
with small delay, like 10-50ms. At every run, the function updates the element.
Actually, there is no restriction on “element”. Anything can be animated.
The generic animation framework accepts following parameters:
delay
- the delay between frames.duration
- the total time for the animation.delta
- the function to calculate animation state at every frame. The functions can be modified by applyingeaseOut/easeInOut
transforms.step
- the function which applies the animation state to the element.
Specific animations may accept other parameters: from, to
etc.
If the animation runs slow, consider making the element absolute and using CSS transitions. Also try to handle many animations in single timer.