Wednesday, September 26, 2007

Javascript Animation : basics of easing

Creating an animation in javascript is not very hard nowadays:
- Javascript implementation in main browsers has been normalized to some extend
- Computers CPU can handle more than they use to.

On top of that, Javascript is always a good choice over Flash or Java Applets if you want to have a friendly-search-engine website with little enhancements.
They're still things you need to remember when using Javascript:
- Users can turn javascript off
You should be able to browse your website with javascript turn off (it should degrade gracefully).
Which means that you shouldn't have links created thru javascript for example...
You shouldn't mix javascript with xhtml and try as possible to put the javascript contents in a separate file.
- Search engines don't understand javascript A search engine is blind with absolutely no extra!
It load your pages, parse the html and try to find the contents it can find.
<a href="javascript:go_to_my_page">My link</a>
Is not a friendly search engine link!
Your link will never be crawled then...

Obviously, these are very basic rules but always good to remember when building a website. Now, let's dive into the animation !

The animation

We are going to see, step by step, how to create a very basic animation
that will extend a div width smoothly(with a basic easing).
animate
reset


As you can see the width of this div is expanding fastly at the beginning and reaches the final width slowly. This is what we call an easing effect.
The code

First let's see the html code that will contain the style in-line (xhtml and style should not be merged but this will simplify
greatly the javascript code for now).
And for readability, I didn't include the meta, head tags...

html+css :
<div id ="bluesquare"
style ="width:150px;height:150px;background-color:blue"> </div>

As the id specify, this draws a wonderful blue square:
The div id will allow us to find this html element thru javascript very easily thanks to the getElementById() function.

First we need to get the width of the html element:
var x = elm.style.width;

where elm is the object we are targeting, here an html div element.
we can get this object thru the following script:
var elm = document.getElementById('bluesquare');

we can then access the attributes of this div:
alert(elm.id); // display bluesquare; 
alert(elm.style.width) // display 150px; 
So we want to animate the width of an element and we do have the ingredients !
Now, let's look at the function:
function changeWidth(elm,toWidth) {
//do the animation here
}
Here is how will call the function:
changeWidth(document.getElementById('bluesquare'),400);
We want to go from elm.style.width (150) to 400.
At first, we could try to loop thru these values and add a constant (1 or 10px...) to the actual elm.style.width.
var beginningWidth = parseInt(elm.style.width);
var add       = beginningWidth+10;
elm.style.width  = add+'px';
We apply the parseInt function to the return value of elm.style.width as it contains the unit (generally 'px').
the parseInt will loop thru each element of the string and stop at the first element that is not a number and therefore send us back 150 (as a number and not a string).
Now, we could try the following to create the effect:
function changeWidth(elm,toWidth) {
      var beginningWidth = parseInt(elm.style.width);
      for(i=beginningWidth;i<=toWidth;i+=10) {
           var add       = beginningWidth+i;
           elm.style.width  = add+'px';
      }
}
This seems to be logical, right ?
Let's try it !
animate
reset
Well, it is not what we were expecting !
In fact, the animation is simply to fast and we cannot see the movement.
(in fact, this is a little bit different from that, see here...)
Changing the way the counter is incremented won't help...
So we need to change our plan.
(for reference' sake there isn't any sleep or wait function)
let's do basic:
// add 10 pixels to the width of the element
function changeWidth(elm,toWidth) {
       var beginningWidth = parseInt(elm.style.width)+10;
       elm.style.width  = beginningWidth+'px';
}
then we could call the function :
changeWidth(document.getElementById('bluesquare'),400);
changeWidth(document.getElementById('bluesquare'),400);
changeWidth(document.getElementById('bluesquare'),400);
...
well,well, ok, I know this is not going anywhere !
But here, we are calling the same function several times,increasing the width by 10 pixels each time and this is going to be the main principle of the new function !
With the above code, the function will be executed one after another very quickly with absolutely no control of the time.
That is why we will use the setTimeout function :
setTimeout(function,delay);
The setTimeout function accepts a function and a delay in milliseconds as parameters.
This function, with setInterval (which takes exactly the same arguments), will allow us to 'loop', call one function several time.
For example:
function animateThat() {
   var timer=setTimeout(somethingReallyCool,1000);
    aFunctionNameThatIsLongAndDoesntHelpThatMuch();
}
Here, when you will call the animateThat function, the setTimeout will launch the somethingReallyCool function in 1 s.
The setInterval function will launch repeatedly the function according to the interval :
function animateThat() {
    var timer=setInterval(somethingReallyCool,1000);
    aFunctionNameThatIsLongAndDoesntHelpThatMuch();
}
This will call the somethingReallyCool function every 1 s.
We keep the return value of the setInterval or setTimeout in the timer variable because this is an identifier that will allow us to stop the function like so:
clearInterval(timer);
clearTimeout(timer);
And it will stop the function associated with this timer.
One thing to notice : you cannot put arguments to the function taken by setTimeout.
setTimeout(changeWidth(document.getElementById('bluesquare'),400),1000);
This won't work.
We could write it this way though :
setTimeout('changeWidth('+document.getElementById('bluesquare')+',400)',1000);
But this will be soon be a nightmare when you will have several parameters to pass to the setTimeout function.
(by the way, did you notice the error in the above setting ?)
(ok, double quotations are my friends is the hint!) There is a simpler solution:
function wrapchangeWidth() {
    changeWidth(document.getElementById('bluesquare'),400),
}
setTimeout(wrapchangeWidth,1000);
This a lot more readable and you will thank yourself for doing this later!
So now that you know how to create 'loops' thru setTimeout and setInterval, let's see how to use them in our animation !
we can then try:
function wrapchangeWidth() {
   changeWidth(document.getElementById('bluesquare'),400);
}
for(i=1;i<=10;i++){
setTimeout(wrapchangeWidth,100*i);
}
The function will expand the width over time.
the delay value is changed thru the loop to get the animation.
But we do not have real control here as the function will fire as soon as the loop starts.
function wrapchangeWidth() {
   changeWidth(document.getElementById('square'),700);
}
function changeWidthForReal() {
    for(i=1;i<=10;i++){
     setTimeout(wrapchangeWidth,100*i);
    }
}
The naming is going to be messy here !
anyway, you can call the function whenever you want :
animate
reset
This is getting closer to what we want here !
But this is not perfect :
- we have set a value that is not related to the width we want to reach
- we do not control the end of the animation
- we need to write quite a lot of code for just one animation !
- the animation is very linear and mechanical.

So let's do something for the 3 first statements with the following function:
function changeWidth(elm,toWidth) {
        //get the actual width of the element
        var actualWidth = parseInt(elm.style.width);
       //calculate the change between the actual element
       //width and the final width
       var change     = toWidth-actualWidth;
      //we add 10px to actual width
      var add=actualWidth+10;
      elm.style.width  = add+'px';
      //we wrap the changeWidth to pass it to the settimeout
      function innerChangeWidth() {
          changeWidth(elm,toWidth);
      }
      //if the actual width and the final width difference
      // is superior to 0, we still need to improve the width
      if(change>0){
         timer = setTimeout(innerChangeWidth,100);
      }
      //else we clear the timeout and stop the function.
      else {
        clearTimeout(timer);
        return true;
     }
}
ok,this one is huge and requires some explanation:
Basically, the function is calling itself recursively and increasing the width by 10px each time.
As we need to stop the function once the element width as reach the final width(400), we calculate the change between the actual width and the width we want to reach :
first call : width:150px+10px; change : 400px-160px;(240)
second call : width:160px+10px; change : 400px-170px;(230)
...
until the change is equal to 0 which will mean that we have reach the final width.
Therefore, if the change is not equal to 0, we continue to call the function thru the timeout.
The function is calling itself thru the setTimeout in order to give us some control over the speed of the animation
So here, with have written a function within a function and this is a very powerful concept in javascript.
You can read a simple explanation of how it works, here.

So we need to add this little extra that will render a more lively animation that will start quick at the beginning and end slowly.
We are going to use a very basic idea :
The width will grow of the half of the difference between the actual width and the final width...
Alright, put it simply :
first loop : width:150px +((400px-150px)/2)=>275px (increase of 125px)
second loop : width:275px +((400px-275px)/2)=>337.5px (increase of 62.5px) as you can see the first loop will increase the width twice more than the second one. the third loop will increase half of the second loop increase value and so on.
With this very basic calculation will have an animation increasing the width in a different speed, creating this easing effect.
Let's see the last function :
function changeWidth(elm,toWidth){
 var width  = parseInt(elm.style.width);
 var change = toWidth-width;
 var total=width+Math.ceil((change/2));
 elm.style.width=total+'px';
 function c() {
       changeWidth(elm,toWidth);
 }
 if(change==0) {
  clearTimeout(timer);
  return;
 }
 timer=setTimeout(c,100);
}
This function is almost the same as the precedent one.
The main difference is on the way we increase the value of the width.
We have added a ceil function to be sure the result will be rounded and therefore allow us to check for the change being equal to 0.

You can have a look at the function and set the delay value.
The changement will be output to the screen.
total:150px; change:0px
Delay:
animate
reset

Conclusion

We have seen a very basic way to create an animation.
You could change it to have the height of the div increasing.
You could use this function for a menu for example that will grow the width of the div to display a picture on mouseover.
(Remember that you should be able to navigate thru your website with javascript off ! So don't put contents that could be hidden to the user if they do not have javascript turn on)

Although this function creates a little nice effect, drawbacks are numerous :
You cannot control the duration of the animation : it will get there when it will get there !
you can try to change the setTimeout delay but this is going to be painfull very quickly !
This is only a very basic ease out that increases too quickly the width at the beginning and too slowly at the end !
You cannot do nested easing like an ease in or a ease in/out.
As you can see this is a very simple starter but hope this was usefull anyway.
By the way, here are the functions we have seen:
- getElementById
- setTimeout
- clearTimeout
- setInterval
- clearInterval
- parseInt
- Math.ceil

5 comments:

Charles T said...

Thanks for the animation tutorial. I've learned a lot from this one article. Matter of fact my slide functions have only gotten as far as your second to last example. No easing at all. This has been very informative!!

shiriru said...

I am glad that this entry has been helpful!
If you want to learn how to create reacher animations, keep on reading the Javascript Animation series as it will give you more and more control and possibilities!

Thank you for your comment!

Anonymous said...

Fantastic tut. I found that a smoother transition can be achieved with these numbers:

total = width + Math.ceil( ( change / 12 ) );
timer = setTimeout( c, 10 );

On to the next...

onirix said...

Excellent ! It’s exactly what I searching for hours ’:|

Thanks

Anonymous said...

Thank you very much