Saturday, January 19, 2008

Javascript Animation : dynamic fps with setInterval

if you read the 'javascript animation' entries, you know that we built an animation function using setTimeout as a base. duration, fps, multiple settings, easing were also available! let's an other way of implementing this animation function by using setInterval

Refresh, refresh!


Before we go on with the new implementation, let's see where we left the animation first!
If you read the 'javascript namespaces' entries, you will notice that everything is in the global space but for now, let's pretend you didn't see it!
So here we go:
function animate(elm,props, duration, fps,easing) {

   duration = parseFloat(duration) || 1000;
   fps      = parseFloat(fps)      || 30;
   easing   = easing || function(t,b,c,d) {
                                  return b + (c/d)*t;
                        };
   var interval    = Math.ceil(1000/fps);
   var totalframes = Math.ceil(duration/interval);

   for(var i=1;i <= totalframes;i++) {
     (function() {
       var frame=i;
       var setAnimation=function() {
         for(var prop in props){
           strategyFactory(totalframes,frame,prop,props,easing);
         }
       }
       var timer = setTimeout(setAnimation,interval*frame);
     })();  
  }
}

function strategyFactory(totalframes,frame,prop,props,easing) {
     var start=props[prop].start;
     if(/[0-9]+/.test(parseInt(start)) && !/\s/.test(start)) {
         NumericStrategy(totalframes,frame,prop,props,easing);
         return true;
     }
     var strategy=camelize(firstToUpperCase(prop));
     if(window[strategy]) {
        window[strategy](totalframes,frame,prop,props,easing);
        return true;
     }
     return false;
}

How it works

We are not going to review all the entries but put it simply:
- we first set the basic options like the duration, the frame rate per second
- we calculate the interval and the total number of frames required
- then we loop thru each frames, create a closure just to scope the counter i into the setAnimation function
- delegate all the calculation to the strategyFactory
- change the interval of the timeout according to the actual frame.

This is basically how it works, you can read past entries entitled 'javascript Animation :' to get more detail.
So in that case, we are using setTimeout, closure and a dynamic interval.
Let's see how to get the same result but with setInterval!

setInterval : dynamic frame rate


If with setTimeout we were looping thru each frames until the end, with setInterval, we are going to calculate the acutal frame and determine if we shall stop or continue by taking into account the elapsedtime!
The strategyFactory is the same, so let's put the animate function only:
function animate(elm,props, duration, fps,easing) {

   duration = parseFloat(duration) || 1000;
   fps      = parseFloat(fps)      || 30;
   easing   = easing || function(t,b,c,d) {
                                  return b + (c/d)*t;
                        };
   var interval    = Math.ceil(1000/fps);
   var totalframes = Math.ceil(duration/interval);

   var startTime  = new Date().getTime();
   var frame      = 1;
   var timer      = null;

   var setAnimation=function() {
       var time = new Date().getTime();

         frame = parseInt((time - startTime) / interval);
       for(var prop in props){
         strategyFactory(totalframes,frame,prop,props,easing);
       }
       if(time >= startTime + duration) {
              clearInterval(timer);
       }
  }
  timer = setInterval(setAnimation,interval);
}

There aren't that many changes but let's see step by step:
- we first set the basic options like the duration, the frame rate per second.
- we calculate the interval and the total number of frames required.
- we keep in memory the starting time of the animation and initialize some variables like the frame and the timer.
- then we defined the setAnimation function that calculates dynamically the actual frame by comparing the elapsed time with the interval.
It means that if the application is slow, the actual frame, that should be 12, can be 14 or 17 because the computer can not handle as expected.
Here if you want to get the frame rate per second, just do the following computation:
var actualFPS = parseInt((new Date().getTime() - time)/frame);
Output this in a div to see it evolve (you need to do some heavy stuff around though)
- delegate all the calculation to the strategyFactory
- check if we have reached the duration required and if so stop the interval.
- launch the setAnimation thru a regular interval.

The differences


As you can see using the setInterval method add some calculations that the simple setTimeout didn't require.
Here we need to check the elapsed time to stop the timer and keep in memory the starting time.
As we are using a closure within the main animation function, the startTime is kept in memory and we can also access the timer even after the function has returned.
The advantage of using the setInterval is that you are only going to do what the computer can really handle, nothing more, nothing less.
It allows to reduce the general overhead that can occur when simultanate actions are taking place!

Conclusion

We have seen how we could easily re-implement the animate function by using the setInterval function instead of the setTimeout.
We didn't have to deal with too many unnecessary things as we kept all the computation outside of the main function.
The closure used within the animate function is the premice of object oriented programming, as it allows the function to keep state of variables thru time.
But this is far to be perfect:
- We send bunches of variables to the strategyFactory that delegates these bunches of variables to other functions, which could be made simpler by using an object oriented approach where we could keep several variables internal to the application.
- everything is in the global namespace!
We can easily change this and use the Package function developped in the javascript namespaces entries and clean a little our mess!

We will see in next entries:
- how to namespace everything and use object oriented concepts
- how to deal with borders
- how to use a cascading style sheet to define your animation where it should be : in a css class and stay DRY!
- how to use custom events (onStart,onProgress, onComplete )
,etc!

2 comments:

Anonymous said...

this series has been very helpful, thanks!

Sam Fleming said...

This series of articles has been very helpful for me as well. Thank you greatly!

I am just wondering, if you get time, could you do add another article for this series so you can animate to a predefined css class?

Thanks again!