Saturday, September 29, 2007

Javascript Animation : Controlling time (2)

As we've seen in the entry basics of easing, we can apply very basic animations to a div by increasing its width.
We could change other properties like its height, opacity or color if we want, allowing some nice effects like fade in, fade out or slide in, slide out...

However,the animation didn't offer any control over the time required to reach the final width.
It will end when it will end !
This is fine if you don't need too complexed animations but being able to set the duration of the animation could be nice !

We will see in this entry how to control the time of the animation developed in basics of easing

What is an animation ?


If we go back a little and think about what is an animation, we could say that an animation offers the evolution of a value over time.
Basically, an animation starts, change something and ends.
the time between the starts and the end is the duration
So an animation will be:

- a subject to animate
- a start point
- a end point
- a duration
Let's take an example to understand a bit more what an animation involves.

Run Forest...


Let's say you want to run 120 meters in 15 seconds.
- The start line represents the beginning, 0 meter.
- The arrival point represents the end, 100th meter.
- The change between the start and the end is: chane=end-start,120-0=120
(In that case, the change is equal to the end)
- 15 seconds represent the duration.
If you want to reach 120 meters in 15 seconds, what is the number of meters(velocity) you must run per second ?
velocity = end/duration = 120/15 = 8 m/s.

We can then find back the reach point:

end = duration*velocity = 15*8 = 120 meters

Animation


Let's go back to our width animation by keeping in mind the runner example which gives a good idea of what is an animation, something in movement over time.
if our square has a width of 150px and that we want to animate it until 350px in 1000 milliseconds, we will have the following:
-start : 150
-end : 350
-duration : 1000
We will need to increase the width by:
-change : 350-150=200

- velocity = change/duration = 200/1000 = 0.2 px/ms.
We can then find back the reach point:
end = start + duration*velocity = 150+ 1000*0.2 = 350

We calculate the velocity by dividing the change by the duration in milliseconds, which give us the rise per millisecond of the width.
This can be a little bit too much to animate the width per milliseconds basis.

Movies

A movie is a sequence of images placed one after an other.
Human eyes will feel the movement is fluid if within one second you have 24 images.
It means you don't have an image changing each milliseconds !
You have the following interval:

interval=1000/24=41.66666666...

It means that we have one image every 41 milliseconds.

Animation and frames

We are going to use the same idea and use a rate that will give us the number of steps to perform the animation.
In our movie example, 24 is the number of steps or frames per second.
This is what you can add now;
- start : 150
- end : 350
- change : 350-150=200
- duration : 1000
- frames/s : 20

In that case, the interval will be :

interval = duration/frames = 1000/20 = 50

So what does it mean ?
In other words, we will have 20 frames (images for a movie) per second to create the animation.
which means that we'll animate every 50 milliseconds the width of the div 20 times or frames.

And if I want my animation to last 1700 milliseconds with 20 frames per second ??
We can find the total number of frames very easily;

interval per second = 1000/frames= 1000/20 = 50
frames required for the animation = duration/interval per second = 1700/50 = 34

It means that to perform a 20 frames per second base animation over 1700 seconds, we will need 34 frames in total.

This may seem a little bit confusing but let's see the code that will implement everything we have seen so far !

The code



First let's see how we are going to use the function;
//general definition
function changeWith(elm,begin,end,duration,fps);
//example
changeWidth(document.getElementById('bluesquare'),150,400,1500,20);

Our function takes as parameters everything we have seen so far.
So we will animate a element with an id of 'bluesquare' extends its width to 400px in 1 second and a half and with 20 frames per second.
Here is the html and css code:

HTML+CSS:
<style type="text/css">
#bluesquare {
width:150px;
height:150px;
background-color:blue;
}
</style>
<div id="bluesquare"></div>

The javascript code


We first need to do some calculation with the value passed to the function.
If you remember, we were calculating interval, change, total number of frames...
function changeWidth(elm,begin,end,duration,fps) {

       var change      = end-begin;
       var interval    = 1000/fps;
       var totalFrames = duration/interval;
       var fixedStep   = change/totalFrames;

       //code continues...
}
changeWidth(document.getElementById('bluesquare'),150,400,1500,20);

So first we calculate the difference between the start width and the end width.
In that case, it will be 400-150=250
Then we calculate the interval per second according to the frame rate.
(for a movie,we were calculating 24 images per second = 1000/24 =41.6666... milliseconds of interval between the images)
In that case, we will get 1000/20=50 milliseconds
As the animation last more than one seconds, the total of frames will differ from the frame per second rate, and we will get:
total frames = duration/interval = 1500/50 = 30

In the end, we calculate a simple constant that will tell us how many pixels we must add at every frame, which is to say every 50 milliseconds, to reach the 400 pixels.
fixedStep=change/totalFrames=400/30 = 13.33333... You can see that we have rounded the value of the interval.
We keep the fixedStep precise has it will help reach the final value of the animation.

So what's next ??
We know that we will animate a total of 30 frames spaced by 50 milliseconds for a total of 1500 milliseconds.
Each frame will add 13.333 pixels to the total of the element width and reach 400px.
A loop that goes from 1 to 30 frames will just do the job!
//...
for(i=1;i <= totalFrames;i++) {

 (function() {
      var n = i;
      function innerChangeWidth() {
           var width        = begin+(fixedStep*n);
           elm.style.width  = width+'px';
      }
      timer = setTimeout(innerChangeWidth,interval*n);
  })();
}
So we are looping thru the number of frames.
We capture the value of the actual frame within an anonymous function.
Then we calculate the value of the width :
var width        = begin+(fixedStep*n);
first frame:
var width        = 150 + (13.3333*1) = 163.3333
second frame:
var width        = 150 + (13.3333*2)= 176.6666
....
and add it to the elem.
We put these calculations within a inner function as we are not applying the new width directly but according to the value of the interval.
Therefore we set the function to a setTimeout with a delay calculated according to the frame:
timer = setTimeout(innerChangeWidth,interval*n);
first frame:
timer = setTimeout(innerChangeWidth,50*1);
second frame:
timer = setTimeout(innerChangeWidth,50*2);
...
As you can see the interval will grow accordingly from 50 to 100, then 150 until 1500 that will be the end !
If you do not understand the anonymous function and the inner function, you can have a quick look at my inner request for outer that explains the basics of this.
If we glue the pieces together, this is what we get:

function changeWidth(elm,begin,end,duration,fps) {

    var change      = end-begin;
    var interval    = Math.ceil(1000/fps);
    var totalFrames = Math.ceil(duration/interval);
    var fixedStep   = change/totalFrames;

    for(i=0;i < totalFrames;i++) {
     (function() {
         var n = i;
         function innerChangeWidth() {
              var width        = begin+(fixedStep*n);
              elm.style.width  = width+'px';
         }
         timer = setTimeout(innerChangeWidth,interval*n);
     })();
   }
}

Finally some animations here!


It's been certainly boring some times or not very clear, but we can finally see the result of this function and if you came that far, I applaud !

test

What's next ?


As you can see we have taken control over time !
This was a little bit long but I hope you have grasped the concepts.
We called this function 'changeWidth' but hey, why not make it a little bit more flexible ?
I guess we can do the following animations with very few changes:

- width
- height
- left
- top
- opacity

Refactoring the code

As I told you, we can keep the same function and change just a few things:
function animate(elm,prop,begin,end,duration,fps) {

        if(!duration) duration=1000;
        if(!fps) fps = 12;

        var change       = end-begin;
        var interval     = Math.ceil(1000/fps);
        var totalFrames  = Math.ceil(duration/interval);
        var fixedStep    = change/totalFrames;
            
        for(i=1;i<=totalFrames;i++) {
           (function() {
              var n=i;
              function innerChangeWidth() {
                 var increase=begin+(step*n);
                 unit=(elm=='opacity') ? '' : 'px';
                 if(window.attachEvent && !unit) { 
                      increase*=100; 
                      obj.style.zoom = 1;
                      obj.style.filter = "alpha(opacity=" + increase + ")";
                 } else {
                     obj.style[elm]  = increase+unit; 
                 }
             }
             timer = setTimeout(innerChangeWidth,interval*n);
           })();
       }
}
//example
animate(document.getElementById('bluesquare'),'width',150,400,1000,12);
//or 
animate(document.getElementById('bluesquare'),'width',150,400);
animate(document.getElementById('bluesquare'),'height',150,400);
animate(document.getElementById('bluesquare'),'top',0,100);
animate(document.getElementById('bluesquare'),'left',0,150);
animate(document.getElementById('bluesquare'),'opacity',0.2,0.8);
So what did we do ?
Well, we added one parameter,prop, to the function that is in charge of specifying the property on which the animation will work: width, height, top,left, opacity.
We set some default value to the duration and the fps.
There is may be an obscure part related to the opacity, but I will link to this subject later as it is just a cross-browser fix.

We are now able to move the div, create a slide in, slide out, fade in, fade out, slide in horizontal, slide out horizontal !
With one function !


Start:
End:
Duration:
FPS:
test

Conclusion


In one function, we are now able to create different animations that will work on different properties, have a desired duration and frames !
We know control the time but this is far to be perfect...

We focus ourself on being able to play with the time, which in term will perhaps allow us to go back and forth in time, rewind, forward the animation as will but we forgot one element:

EASING

As we are just adding a constant to each new frame to the animated element, the animation is very mechanical and is a little bit boring.
We need to find a way to create some easing in/out like we did in the first entry so that the animation will look more appealing by having not a constant added to each frame !
Now, that we control the time and different parameters of the animation, we might be able to create some cool easing in, easing out, easing in/out !

No comments: