Menu Search
Jump to the content X
X

A Quick Look Into The Math Of Animations With JavaScript

Advertisement

In school, I hated math. It was a dire, dry and boring thing with stuffy old books and very theoretical problems. Even worse, a lot of the tasks were repetitive, with a simple logical change in every iteration (dividing numbers by hand, differentials, etc.). It was exactly the reason why we invented computers. Suffice it to say, a lot of my math homework was actually done by my trusty Commodore 64 and some lines of Basic, with me just copying the results later on.

These tools and the few geometry lessons I had gave me the time and inspiration to make math interesting for myself. I did this first and foremost by creating visual effects that followed mathematical rules in demos, intros and other seemingly pointless things.

There is a lot of math in the visual things we do, even if we don’t realize it. If you want to make something look natural and move naturally, you need to add a bit of physics and rounding to it. Nature doesn’t work in right angles or linear acceleration. This is why zombies in movies are so creepy. This was covered here before in relation to CSS animation, but today let’s go a bit deeper and look at the simple math behind the smooth looks.

Going From 0 To 1 Without Being Boring Link

If you’ve just started programming and are asked to go from 0 to 1 with a few steps in between, you would probably go for a for loop:

for ( i = 0; i <= 1; i += 0.1 ) {
  x = i;
  y = i;
…
}

This would result in a line on a graph that is 45 degrees. Nothing in nature moves with this precision:

45 degree graph, a result of a simple for loop

A simple way to make this movement a bit more natural would be to simply multiply the value by itself:

for ( i = 0; i <= 1; i += 0.1 ) {
  x = i;
  y = i * i;
}

This means that 0.1 is 0.01, 0.2 is 0.04, 0.3 is 0.09, 0.4 is 0.16, 0.5 is 0.25 and so on. The result is a curve that starts flat and then gets steeper towards the end:

graph of the value multiplied with itself

You can make this even more pronounced by continuing to multiply or by using the “to the power of” Math.pow() function:

for ( i = 0; i <= 1; i += 0.1 ) {
  x = i;
  y = Math.pow( i, 4 );
}

graph with several multiplications

This is one of the tricks of the easing functions used in libraries such as jQuery and YUI, as well as in CSS transitions and animations in modern browsers.

You can use this the same way, but there is an even simpler option for getting a value between 0 and 1 that follows a natural motion.

Not A Sin, Just A Natural Motion Link

Sine waves are probably the best thing ever for smooth animation. They happen in nature: witness a spring with a weight on it, ocean waves, sound and light.
In our case, we want to move from 0 to 1 smoothly.

To create a movement that goes from 0 to 1 and back to 0 smoothly, we can use a sine wave that goes from 0 to π in a few steps. The full sine wave going from 0 to π × 2 (i.e. a whole circle) would result in values from -1 to 1, and we don’t want that (yet).

var counter = 0;

// 100 iterations
var increase = Math.PI / 100;

for ( i = 0; i <= 1; i += 0.01 ) {
  x = i;
  y = Math.sin(counter);
  counter += increase;
}

half a sine wave

A quick aside on numbers for sine and cosine: Both Math.sin() and Math.cos() take as the parameter an angle that should be in radians. As humans, however, degrees ranging from 0 to 360 are much easier to read. That’s why you can and should convert between them with this simple formula:

var toRadian = Math.PI / 180;
var toDegree = 180 / Math.PI;

var angle = 30;

var angleInRadians = angle * toRadian;
var angleInDegrees = angleInRadians * toDegree;

Back to our sine waves. You can play with this a lot. For example, you could use the absolute value of a full 2 × π loop:

var counter = 0;
// 100 iterations
var increase = Math.PI * 2 / 100;

for ( i = 0; i <= 1; i += 0.01 ) {
  x = i;
  y = Math.abs( Math.sin( counter ) );
  counter += increase;
}

absolute value of a sine wave

But again, this looks dirty. If you want the full up and down, without a break in the middle, then you need to shift the values. You have to half the sine and then add 0.5 to it:

var counter = 0;
// 100 iterations
var increase = Math.PI * 2 / 100;

for ( i = 0; i <= 1; i += 0.01 ) {
  x = i;
  y = Math.sin( counter ) / 2 + 0.5;
  counter += increase;
}

halfed and moved sine wave

So, how can you use this? Having a function that returns -1 to 1 to whatever you feed it can be very cool. All you need to do is multiply it by the values that you want and add an offset to avoid negative numbers.

For example, check out this sine movement demo:

Sine jump demo

Looks neat, doesn’t it? A lot of the trickery is already in the CSS:

.stage {
  width:200px;
  height:200px;
  margin:2em;
  position:relative;
  background:#6cf;
  overflow:hidden;
}

.stage div {
  line-height:40px;
  width:100%;
  text-align:center;
  background:#369;
  color:#fff;
  font-weight:bold;
  position:absolute;
}

The stage element has a fixed dimension and is positioned relative. This means that everything that is positioned absolutely inside it will be relative to the element itself.

The div inside the stage is 40 pixels high and positioned absolutely. Now, all we need to do is move the div with JavaScript in a sine wave:

var banner = document.querySelector( '.stage div' ),
    start = 0;
function sine(){
  banner.style.top = 50 * Math.sin( start ) + 80 + 'px';
  start += 0.05;
}
window.setInterval( sine, 1000/30 );

The start value changes constantly, and with Math.sin() we get a nice wave movement. We multiply this by 50 to get a wider wave, and we add 80 pixels to center it in the stage element. Yes, the element is 200 pixels high and 100 is half of that, but because the banner is 40 pixels high, we need to subtract half of that to center it.

Right now, this is a simple up-and-down movement. Nothing stops you, though, from making it more interesting. The multiplying factor of 50, for example, could be a sine wave itself with a different value:

var banner = document.querySelector( '.stage div' ),
    start = 0,
    multiplier = 0;
function sine(){
  multiplier = 50 * Math.sin( start * 2 );
  banner.style.top = multiplier * Math.sin( start ) + 80 + 'px';
  start += 0.05;
}
window.setInterval( sine, 1000/30 );

The result of this is a banner that seems to tentatively move up and down. Back in the day and on the very slow Commodore 64, calculating the sine wave live was too slow. Instead, we had tools to generate sine tables (arrays, if you will), and we plotted those directly. One of the tools for creating great sine waves so that you could have bouncing scroll texts was the Wix Bouncer:

Wixbouncer - a tool to create sine waves

Circles In The Sand, Round And Round… Link

Circular motion is a thing of beauty. It pleases the eye, reminds us of spinning wheels and the earth we stand on, and in general has a “this is not computer stuff” feel to it. The math of plotting something on a circle is not hard.

It goes back to Pythagoras, who, as rumor has it, drew a lot of circles in the sand until he found his famous theorem. If you want to use all the good stuff that comes from this theorem, then try to find a triangle with a right angle. If this triangle’s hypothenuse is 1, then you can easily calculate the horizontal leg as the cosine of the angle and the vertical leg as the sine of the angle:

Sincos

How is this relevant to a circle? Well, it is pretty simple to find a right-angle triangle in a circle to every point of it:

Circle

This means that if you want to plot something on a circle (or draw one), you can do it with a loop and sine and cosine. A full circle is 360°, or 2 × π in radians. We could have a go at it — but first, some plotting code needs to be done.

A Quick DOM Plotting Routine Link

Normally, my weapon of choice here would be canvas, but in order to play nice with older browsers, let’s do it in plain DOM. The following helper function adds div elements to a stage element and allows us to position them, change their dimensions, set their color, change their content and rotate them without having to go through the annoying style settings on DOM elements.

Plot = function ( stage ) {

  this.setDimensions = function( x, y ) {
    this.elm.style.width = x + 'px';
    this.elm.style.height = y + 'px';
    this.width = x;
    this.height = y;
  }
  this.position = function( x, y ) {
    var xoffset = arguments[2] ? 0 : this.width / 2;
    var yoffset = arguments[2] ? 0 : this.height / 2;
    this.elm.style.left = (x - xoffset) + 'px';
    this.elm.style.top = (y - yoffset) + 'px';
    this.x = x;
    this.y = y;
  };
  this.setbackground = function( col ) {
    this.elm.style.background = col;
  }
  this.kill = function() {
    stage.removeChild( this.elm );
  }
  this.rotate = function( str ) {
    this.elm.style.webkitTransform = this.elm.style.MozTransform =
    this.elm.style.OTransform = this.elm.style.transform =
    'rotate('+str+')';
  }
  this.content = function( content ) {
    this.elm.innerHTML = content;
  }
  this.round = function( round ) {
    this.elm.style.borderRadius = round ? '50%/50%' : '';
  }
  this.elm = document.createElement( 'div' );
  this.elm.style.position = 'absolute';
  stage.appendChild( this.elm );

};

The only things that might be new here are the transformation with different browser prefixes and the positioning. People often make the mistake of creating a div with the dimensions w and h and then set it to x and y on the screen. This means you will always have to deal with the offset of the height and width. By subtracting half the width and height before positioning the div, you really set it where you want it — regardless of the dimensions. Here’s a proof:

offset issue

Now, let’s use that to plot 10 rectangles in a circle, shall we?

Simple circle

var stage = document.querySelector('.stage'),
    plots = 10;
    increase = Math.PI * 2 / plots,
    angle = 0,
    x = 0,
    y = 0;

for( var i = 0; i < plots; i++ ) {
  var p = new Plot( stage );
  p.setBackground( 'green' );
  p.setDimensions( 40, 40 );
  x = 100 * Math.cos( angle ) + 200;
  y = 100 * Math.sin( angle ) + 200;
  p.position( x, y );
  angle += increase;
}

We want 10 things in a circle, so we need to find the angle that we want to put them at. A full circle is two times Math.PI, so all we need to do is divide this. The x and y position of our rectangles can be calculated by the angle we want them at. The x is the cosine, and the y is the sine, as explained earlier in the bit on Pythagoras. All we need to do, then, is center the circle that we’re painting in the stage (200,200 is the center of the stage), and we are done. We’ve painted a circle with a radius of 100 pixels on the canvas in 10 steps.

The problem is that this looks terrible. If we really want to plot things on a circle, then their angles should also point to the center, right? For this, we need to calculate the tangent of the right-angle square, as explained in this charming “Math is fun” page. In JavaScript, we can use Math.atan2() as a shortcut. The result looks much better:

Tancircle

var stage = document.querySelector('.stage'),
    plots = 10;
    increase = Math.PI * 2 / plots,
    angle = 0,
    x = 0,
    y = 0;

for( var i = 0; i < plots; i++ ) {
  var p = new Plot( stage );
  p.setBackground( 'green' );
  p.setDimensions( 40, 40 );
  x = 100 * Math.cos( angle ) + 200;
  y = 100 * Math.sin( angle ) + 200;
  p.rotate( Math.atan2( y - 200, x - 200 ) + 'rad' );
  p.position( x, y );
  angle += increase;
}

Notice that the rotate transformation in CSS helps us heaps in this case. Otherwise, the math to rotate our rectangles would be much less fun. CSS transformations also take radians and degrees as their value. In this case, we use rad; if you want to rotate with degrees, simply use deg as the value.

How about animating the circle now? Well, the first thing to do is change the script a bit, because we don’t want to have to keep creating new plots. Other than that, all we need to do to rotate the circle is to keep increasing the start angle:

var stage = document.querySelector('.stage'),
    plots = 10;
    increase = Math.PI * 2 / plots,
    angle = 0,
    x = 0,
    y = 0,
    plotcache = [];

for( var i = 0; i < plots; i++ ) {
  var p = new Plot( stage );
  p.setBackground( 'green' );
  p.setDimensions( 40, 40 );
  plotcache.push( p );
}

function rotate(){
  for( var i = 0; i < plots; i++ ) {
    x = 100 * Math.cos( angle ) + 200;
    y = 100 * Math.sin( angle ) + 200;
    plotcache[ i ].rotate( Math.atan2( y - 200, x - 200 ) + 'rad' );
    plotcache[ i ].position( x, y );
    angle += increase;
  }
  angle += 0.06;
}

setInterval( rotate, 1000/30 );

Want more? How about a rotating text message based on this? The tricky thing about this is that we also need to turn the characters 90° on each iteration:

Rotated message

var stage = document.querySelector('.stage'),
    message = 'Smashing Magazine '.toUpperCase(),
    plots = message.length;
    increase = Math.PI * 2 / plots,
    angle = -Math.PI,
    turnangle = 0,
    x = 0,
    y = 0,
    plotcache = [];

for( var i = 0; i < plots; i++ ) {
  var p = new Plot( stage );
  p.content( message.substr(i,1) );
  p.setDimensions( 40, 40 );
  plotcache.push( p );
}
function rotate(){
  for( var i = 0; i < plots; i++ ) {
    x = 100 * Math.cos( angle ) + 200;
    y = 100 * Math.sin( angle ) + 200;
    // rotation and rotating the text 90 degrees
    turnangle = Math.atan2( y - 200, x - 200 ) * 180 / Math.PI + 90 + 'deg';
    plotcache[ i ].rotate( turnangle );
    plotcache[ i ].position( x, y );
    angle += increase;
  }
  angle += 0.06;
}

setInterval( rotate, 1000/40 );

Again, nothing here is fixed. You can make the radius of the circle change constantly, as we did with the bouncing banner message earlier (below is only an excerpt):

multiplier = 80 * Math.sin( angle );
for( var i = 0; i < plots; i++ ) {
  x = multiplier * Math.cos( angle ) + 200;
  y = multiplier * Math.sin( angle ) + 200;
  turnangle = Math.atan2( y - 200, x - 200 ) * 180 / Math.PI + 90 + 'deg';
  plotcache[ i ].rotate( turnangle );
  plotcache[ i ].position( x, y );
  angle += increase;
}
angle += 0.06;

And, of course, you can move the center of the circle, too:

rx = 50 * Math.cos( angle ) + 200;
ry = 50 * Math.sin( angle ) + 200;
for( var i = 0; i < plots; i++ ) {
  x = 100 * Math.cos( angle ) + rx;
  y = 100 * Math.sin( angle ) + ry;
  turnangle = Math.atan2( y - ry, x - rx ) * 180 / Math.PI + 90 + 'deg';
  plotcache[ i ].rotate( turnangle );
  plotcache[ i ].position( x, y );
  angle += increase;
}
angle += 0.06;

For a final tip, how about allowing only a certain range of coordinates?

function rotate() {
  rx = 70 * Math.cos( angle ) + 200;
  ry = 70 * Math.sin( angle ) + 200;
  for( var i = 0; i < plots; i++ ) {
    x = 100 * Math.cos( angle ) + rx;
    y = 100 * Math.sin( angle ) + ry;
    x = contain( 70, 320, x );
    y = contain( 70, 320, y );
    turnangle = Math.atan2( y - ry, x - rx ) * 180 / Math.PI + 90 + 'deg';
    plotcache[ i ].rotate( turnangle );
    plotcache[ i ].position( x, y );
    angle += increase;
  }
  angle += 0.06;
}
function contain( min, max, value ) {
  return Math.min( max, Math.max( min, value ) );
}

Summary Link

This was just a quick introduction to using exponentials and sine waves and to plotting things on a circle. Have a go with the code, and play with the numbers. It is amazing how many cool effects you can create with a few changes to the radius or by multiplying the angles. To make it easier for you to do this, below are the examples on JSFiddle to play with:

(il) (al)

Smashing Book #5

Hold on tiger! Thank you for reading the article. Did you know that we also publish printed books and run friendly conferences – crafted for pros like you? For example, Smashing Book 5, packed with smart responsive design patterns and techniques.

↑ Back to top Tweet itShare on Facebook

An international Developer Evangelist working for Mozilla in the lovely town of London, England.

Advertisement
  1. 1

    Very cool and interesting article!

    9
  2. 2

    Excellent. Good stuff for those of us who mostly suck at math. :)

    3
  3. 3

    Actually one of the best articles on Smashing for a while! Excellent :)

    5
  4. 4

    Yves Van Broekhoven

    October 4, 2011 3:48 am

    Now this is an extremely useful article!

    5
  5. 5

    My first time to comment. Thank you Christian, this is a wonderful article.

    6
  6. 6

    This is outstanding. Well written and clearly explained! I nearly confused the syntax with ActionScript. :) A lot of new programmers struggle with this stuff. Good resource.

    2
  7. 7

    William Lepinski

    October 4, 2011 4:19 am

    Yes indeed. Interesting see how daily things works under the hood.

    5
  8. 8

    Good Approach., Like it !!!

    2
  9. 9

    Great article. Sine really is indispensable. I recently made a scrolling web site where the animation was basically just lots of sine functions with different amplitudes and different periods. The maths isn’t really that hard, but it looks quite complex: http://www.flightofthefireflies.com

    6
  10. 10

    Nice! I learnt math and programming the same way, using old Commodore computers and playing with sines and cosines :) It would be very useful if kids learnt math and programming in the school at the same time, because then they could SEE the math. It then becomes a game, not just theory.

    1
  11. 11

    Great code to hack with. Now all one has to do is put the progress element into these to make for mad hybrid animations:

    (FF6+) Only folks, sorry

    http://jsfiddle.net/higgo85/we54x/2/

    1
  12. 12

    Love this. A far cry from my time at school doing geometry and trigonometry…..

    1
  13. 13

    Rommel Castro A.

    October 4, 2011 8:36 am

    this is a really great article!

    1
  14. 14

    Love it! Thanks!

    1
  15. 15

    Tip: There are tons of used, dusty ol’ Flash math-based animation books out there in second-hand book shops; cheap. The math is the same and effects still cool.

    0
  16. 16

    I wonder how I would do this to pull off a captain skyhawk text effect? Playing now!

    0
  17. 17

    Exactly what I need just right now! Thank you! :-)

    0
  18. 18

    Wow, that’s great!
    But I can’t get the code working, your code works, but the other codes in the article won’t work at all :|
    (by “works” I mean they work on jsfiddle.net and if i create an html with that code in it, it will display this frigging rotating cubes)
    I took the “plot” code and then copy/paste the other codes –>blank screen, or at least a grey square due to the css code…
    Do someone have any ideas on what am I doing wrong?
    BTW nice article!

    0
  19. 19

    Ok, is working, sort of:
    http://jsfiddle.net/zdt4G/

    Half blue, half green :|

    0
  20. 20

    Hmmm … this real good!

    0
  21. 21

    If you want to play arround a little bit more with Animations (using real Math ;-) in JavaScript you might want to check out the Processing.js which is a port of the Processing Visualization Language (Ben Fry and Casey Reas).

    0
  22. 22

    very useful, Thanks.

    1
  23. 23

    thanks a lot.
    useful

    1
  24. 24

    If you look in the last pic, the letters in “Smashing Magazine” is not pointing exaclty to the middle of the circle, rather each of them is pointing a little bit off-center. Seems like the right compensation isn’t 0.5*PI radians (90 deg) but rather 0.45*PI radians (80 deg).
    Had same problem in FF7 and chrome. Tried to set the dimensions of each box smaller, to 10,10. Then it got better. Just a little tip, if you’re using this and seeing the problem, make sure the box size is fairly close to word size.

    Thanks for a cool article!

    0
  25. 25

    Antonio Santiago

    October 5, 2011 7:35 am

    Some time ago I write a short introductory post related to JavaScript and the importance of Maths:

    http://acuriousanimal.com/blog/2010/12/18/javascript-animations-why-maths-are-important/

    Hope can help.

    0
  26. 26

    Top fav article in sm for a while. Less wordpress nonsense, more similar articles. back to roots.

    1
  27. 27

    Love it, I knew math will come handy some day (well I’d hoped)

    0
  28. 28

    You are missing some CSS. Try copying and pasting the CSS from: http://jsfiddle.net/higgo85/we54x/2/ into your document, and you should be fine

    0
  29. 29

    turnangle = Math.PI/2 + angle +’rad’;

    wont this give the same result? (and get rid of atan2)

    0
  30. 30

    Beautifully done! Now I must go and play…!

    0

↑ Back to top