Animation

  1. The basics of the animation
    1. Example
  2. Animation refactored
    1. The generic animation
    2. Example
  3. Maths, the function of progress delta
    1. Linear delta
    2. Power of n
    3. Circ: a piece of circle
    4. Back: the bow function
    5. Bounce
    6. Elastic
  4. Reverse functions (easeIn, easeOut, easeInOut)
    1. easeOut
    2. easeInOut
    3. The plotter
  5. Changing step
    1. Color highlight
    2. Text typing
  6. CSS transitions
    1. Example
    2. Limitations
  7. Optimization hints
  8. 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.

`setInterval` instead of `setTimeout`

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 exceed duration 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 of duration 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 to height 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 the setInterval 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 by delta to interval 0..to. Applies the result to the element.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.


Open in the new window.

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, then easeOut.

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 Smile

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) + ')'
    }
  })  
}

Click to see in action

And now the same, but delta = makeEaseOut(bounce):

Click to see in action

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.

Open solution
Solution

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.

Open solution
Solution

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&lt;4)</span>
</div>

Click to animate (no IE, no FF<4)

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, called transition-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:

  1. Remove the animated element from DOM and attach it directly to BODY at the start. You’ll probably need to make it position: absolute and setup coordinates.
  2. Animate it.
  3. Put it back into the DOM.

The trick can fix shaky animation and save CPU.

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 applying easeOut/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.

See also:

Tutorial

Donate

Donate to this project