Showing posts with label effects. Show all posts
Showing posts with label effects. Show all posts

Tuesday, July 1, 2008

Understand JS and effect library in 10 parts

I've been quite buzy these months with work, my wedding and teeth!
Anyway, I am still in the middle of introduction to JS OOP thru a queue example but I thought it could be nice to sum up all the entries written so for regarding javascript effects and javascript.
These entries, far to be perfect, can be a good base to understand how javascript libraries do their magic (like JQuery,Mootools,scriptaculous,etc) and will indeed allow you to almost create your own library with a little work.

Javascript basics:

These are some side entries that relate to javascript and some cross-browsers fix required, you should read them before getting in the animation series:
My inner request for outer
This entry deals with the basic of javascript closure,anonymous functions which will be used during the animation effect function creation.

Javascript Literal Notation: basics
This entry deals with the basic of javascript literal notation which is the based of JSON and will be used during the animation effect function.

Setting html element style thru Javascript
This entry deals with cross-browser issues that one must deal when setting the style of an html element thru javascript. You will see camelCase, hypenize string and also that some people, in comments, thinks that teaching this is a waste of time. How nice isn't it?

Javascript scoping : Things to keep in mind
This entry deals with javascript scoping issues. If you were to read only one entry above all in these pre-introduction, I will choose this one as you will get a deeper understanding of the this keyword, what is var, when to use it...

javascript : memoizing - caching functions result
This entry deals with the ability of javascript to memoize or overwrite itself to remember a function result. Very usefull for branching functions and could help to improve further some of your functions that needs cross-browser branching for example.

Javascript : Simulating namespaces basic approach, part 1
Javascript : Simulating namespaces thru prototyping part 2
Javascript Namespaces : Import and Export methods
Javascript Namespaces : Privacy thru closure or how to type less!
This entry deals with something that misses in javascript language, the possibility to create namespaces. You will learn what is a namespace, why it is so important in javascript and how to simulate them thru objects. You should enforce your understanding of anonymous function and relate these entries to the javascript function scoping base nature.This hasn't be used while during the animation function but this is definitly something you should be aware of and use if you plan to share your scripts.

Javascript Effects:

These entries will hopefully teach you the basic of javascript animations. These entries will require basic understanding of closure, literal notation,etc. They will teach how works easing and css animation,so go on and read:
1. Javascript Animation : basics of easing
2. Javascript Animation : Controlling time (2)
3. Javascript Animation : take it ease-y ! (3)
4. Javascript Animation : Make things move smoothly (4)
5. Javascript Animation : Changing Colors (5)
6. Javascript Animation : Several elements at once (6)
7. Javascript Animation : Curving the path (7)
8. Javascript Animation : Strategy and Factory Pattern at the rescue (8)
9. Javascript Animation : Implementing Margin and Padding
10. Javascript Animation : dynamic fps with setInterval


I hope these entries will be helpfull to some of view! There are still many things to be seen like effect queuing, custom events, dom ready,namespacing everything, DOM traversial, canvas, etc,etc,etc!!! Therefore, I will update this page when I write a new entry regarding this subject.
If you'd like to get more entries regarding these topics, please leave a comment with some details of what you would like to read!

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!

Tuesday, November 13, 2007

Javascript Animation : Implementing Margin and Padding

After changing our main animation function to a pluggable one, thanks to the strategy associated with the factory pattern, we will see how ce can implement multipart css values like margin and padding.

Differentiating the settings


Many css properties can be written with 3 to 4 values, each of them setting a particular sub property.
The margin and padding properties are one of those and we will just see what each setting means concretly.

The clock wise cycle

As you know a div or a block element can be resumed to a simple square.
This square has a top, right, bottom and left property.
If you think of a square and put each sub properties on their position, you will see that the above describes the movement of a clock wise.
let's see a simple div:
As we can see we have a different color for each line of the square, if you follow each line from top in the clock wise order, you will get:
top
right
bottom
left

Well when you set each properties for the padding or margin property, each value will set the sub property in this order.
margin:1px 2px 3px 4px;
If you want to set the same value to all the sub properties, you just write it once:
margin:1px
If you set only 2 properties then:
margin:1px 2px;
Well, this will distribute each value in this order:
margin-top:1px;
margin-right:2px;
margin-bottom:1px;
margin-left:2px;
And if you set 3 properties?
margin:1px 2px 3px;
margin-top:1px;
margin-right:2px;
margin-bottom:3px;
margin-left:2px;
So, as you can see, it follows a pattern that you can apply to all properties taking up to 4 values.
We can resume as follow:
1 value  [TOP  RIGHT BOTTOM LEFT]
2 values [TOP BOTTOM][RIGHT LEFT]
3 values [TOP][RIGHT LEFT][BOTTOM]
4 values [TOP][RIGHT][BOTTOM][LEFT]

Representing the unusual clock wise


Now that we know what's going on exactly, we just need to find a way to express the same rule in javascript.
First let's create a literal array containing each sub property:
var subProperties=['Top','Right','Bottom','Left'];
If you remember the above explanation, when you set only one value, each sub properties will be assigned this value.
Let's create 4 javascript variables representing each sub properties set in the example above:
var top    =1;
var right  =1;
var bottom =1;
var left   =1;
If we set 2 values, we will get:
var top    =1;
var right  =2;
var bottom =1;
var left   =2;
if we set 3 values, we will get:
var top    =1;
var right  =2;
var bottom =3;
var left   =2;
And if we set 4 values:
var top    =1;
var right  =2;
var bottom =3;
var left   =4;
We can get from there, a litteral array that will be in relation with each sub properties:
var subProperties=['Top','Right','Bottom','Left'];
var subPropertiesValues=[top,right,bottom,left];
If you understood the above, everything will be fine!
It's time to go back a little and see how to separate each values!

Separating the values


We have seen how the values could be written and we know that we have a numeric strategy that handles single numeric-like value for us.
What does it mean?
If we set the margin or padding with only one value, the numeric strategy will do its work!
It means that we do not really have to deal with this case!
Let's see again a margin set with several values:
margin:1px 2px 3px 4px;
Well, what we need to do, is separate each value, set the right sub property and send everything to our numeric strategy!
Let's split the string:
function splitMultiPart(str){
 return str.split(/\s/) || [];
}
Our function will split the string by looking for a space and return an array.
That was easy!
Our array can have different length according to the number of value set.
If there is only one value, it will be of 1 length.
If there is 4 values, it will be a 4 length array.
But we know that an array starts from 0 to access its value.
We can write the following:
var top=0,right=0,bottom=0,left=0;
if(elm.length==2) top=0,right=1,bottom=0,left=1;
if(elm.length==3) top=0,right=1,bottom=2,left=1;
if(elm.length==4) top=0,right=1,bottom=2,left=3;
Here we are not setting the value of each sub properties, but we are simply setting the position of the value in the array we got from the split!
If there is only one value, each sub properties, top, right, bottom, left will be set with the value contained in the unique slot, slot 0.
If there is 4 values, each sub properties, top, right,bottom,left will be set with the value contained in each slot 0,1,2,3.
Let's see the function:
function setMultiPart(totalframes,frame,prop,props,easing){
    
  //split the string of each properties
    var begin  = splitMultiPart(props[prop].start);
    var end    = splitMultiPart(props[prop].end);
    var bezier = (props[prop].bezier)? splitMultiPart(props[prop].bezier):[];
   
  //define each array slot according to the number of values set
    var top=0,right=0,bottom=0,left=0;
    if(values.length==2) top=0,right=1,bottom=0,left=1;
    if(values.length==3) top=0,right=1,bottom=2,left=1;
    if(values.length==4) top=0,right=1,bottom=2,left=3;

 //put each value into one array
    var v = [top,   right,  bottom,  left];
    var p = ['Top','Right','Bottom','Left'];
    var propTmp='';


    for(var i=0;i < v.length;i++) {
          //create one property ie, marginTop,marginRight...
             propTmp  = prop+p[i]; 
          //assing each value to the object
             props[prop].start  = begin[v[i]];
             props[prop].end    = end[v[i]];
             props[prop].bezier = bezier[v[i]];
           //then send the property to the numeric strategy!
             NumericStrategy(totalframes,frame,propTmp,props,easing);
   }
}
As you can see, this is not very hard!
We just split the string by their space, assign each value to their sub property (margin-top,margin-left) and then let the calculation to the numeric strategy!
Now we just need to create wrappers function called Margin and Padding as our intelligent factory is going to call them:
function Margin(totalframes,frame,prop,props,easing) {
   setMultiPart(totalframes,frame,prop,props,easing);
}
function Padding(totalframes,frame,prop,props,easing) {
   setMultiPart(totalframes,frame,prop,props,easing);
}

And we are done!

Conclusion


In this entry, we have seen how we could extend our animation function by extending the css properties it can handle.
As you can see we only focused on resolving the multipart redistribution to each sub property without changing the internal behavior of the animate function!
We are now able to extend when needed the css properties we can handle in a pluggable manner!
There is no example of the actual addition as we will see in a next entry how to change the parameters we send to the function and in the same time add the border property!

Wednesday, November 7, 2007

Javascript Animation : Strategy and Factory Pattern at the rescue (8)

In just 7 entries, we've seen how to start with a basic easing effect dealing with 2 or 3 css attributes at a time and end up with a rich animation function able to deal with time, frames,manipulate several properties at once in a cross browser way, animate colors, create several easing in/out effects and deviate the path thru a bezier curve! but...

The main drawbacks is that we were adding new possibilities directly into the animate function with the help of if...else statements...
If it may be a good work around for a time and for few properties, when the css properties you want to handle grow up, that new css properties pop up, having to add if... else if... else statements in the core function can become cumbersome:
- you may have to handle many new css attributes
- you will need to add more and more if statements, making the function hardly readable - even if you use just 10 percents of the function capacities, you will have to load the entire function - if the code has been working for a while, adding directly in the core can bring bugs, unexpected chaining effects...

In other word, the code will be a mess!!

Here comes the strategy pattern associated with the factory pattern to the rescue !
we will see how to create an expandable function that can be plugged from outside to add new properties like margin or padding thanks to this pattern!

Design pattern what ?


Design patterns are common solutions to common problems.
We have seen above what kind of problems our program could lead to in the long run.
In fact, our program is a solution to the problem but unfortunately not the better one!
The gang of four, authors of design patterns, have brought to us many solutions to many common problems.Solutions thought and re-thought, try and retried to end up to be a 'design pattern'. If you want, a kind of solution template for a recurrent problem !
They all relate to object oriented programming but we will see that we can use the concept behind the patterns in a procedural way too.

Strategy pat... what ?


We have seen that we had to change our effect calculation strategy according to the value we got:
- we had a specific calculation strategy for a simple numeric value like 12px or 1 that could be resumed to simple numbers (12 instead of 12px) or
- we had an other specific calculation strategy for a string that contains the numeric value in a different format like the colors #FF00FF or rgb(255,0,255)!
In order to face this divergence we just added an if statement to handle numeric values and colors.
By doing so, we were aware that the function was becoming messy... hard to read, hard to maintain and not expandable as we could wish.
Let's say that we know want to manipulate margin or padding...
We can see their value structure:
margin:12px;
margin:12px 1px;
margin:12px 1px 5px;
margin:12px 1px 5px 8px;

The same for the padding!
We face 4 different values in one attribute and the number of value changes the sub attribute they relate to !
We could do this at first:
if(numeric) {
//calculation
}
else if (color) {
//calculation 
}
else if (multi) {
//calculation
}
I have shorten up the code but I guess that you can see that if else if else statements (even if disguised under a switch statement!!) is far to be the most expandable way of doing it!
Is there a way to change of strategy automagicly without even having to specify it?
Well, there is!

The Strategy pattern

The strategy pattern allows to change the behavior of part of an algorythm in a class or in our case in a function!
Basically we are going to put outside of the function each strategy in order to keep our core function clean and easy to maintain.
By putting outside the calculation, we will also be able to add new strategies very easily!
Let's see the actual function first:
function animate(elm,props, duration, fps,easing) {

 duration = (duration) ? parseFloat(duration) : 1000;
 fps      = (fps)      ? parseFloat(fps)      : 20;
 easing   = (easing)   ? easing               : linearEase;

 var interval    = Math.ceil(1000/fps);
 var totalframes = Math.ceil(duration/interval);

 for(i=1;i <= totalframes;i++) {
   (function() {
      var frame=i;
      displacement=function() {
          for(var prop in props){
              if(!/olor/.test(prop)) {
                  var begin = props[prop].start*100;
                  var end   = props[prop].end*100;
                  var bezier = props[prop].bezier*100;
                  var actualDisplacement=
                  easing(frame, begin, end-begin, totalframes);
                  if(bezier) 
                    actualDisplacement=
                    QuadBezier(frame,actualDisplacement,end,bezier,totalframes);
                    setStyle(elm,prop,actualDisplacement/100;
                  } else {
                    var b = hexStr2rgbArray(props[prop].start);
                    var e = hexStr2rgbArray(props[prop].end);
                    var rgb=[];
                    for(j=0;j<3;j++) 
                    rgb.push(parseInt(easing(frame, b[j], e[j]-b[j], totalframes)));
                    setStyle(elm,prop,'rgb('+rgb.join(',')+')');  
        }
   }
       }
       timer = setTimeout(generalProperty,interval*frame);
   })();   
 }
}

huh, it's a pretty long function!
We first do some initialisation then some calculation for the entire function then we calculate the animation within a loop.
In order to change our calculation strategy according to the property, we check to see if we have a color and if not we switch to the other one.
we see that this won't work with the margin property anymore...
In order to add the margin,padding calculation strategy within the function we will have to change the way we identify the type of the incoming property, which could lead to errors and eventually, a function that you won't be able to maintain and understand.
Therefore, let's do a simple thing: put outside each strategy!

Identifying the strategies


We have two ways of calculating the property : one for the numeric values and one for the strings that contains an hexadecimal color code.
We can then create two helpers functions:
function NumericStrategy(totalframes,frame,prop,props,easing) {
    var begin  = props[prop].start*100;
    var end    = props[prop].end*100;
    var bezier = props[prop].bezier*100;
    var actualDisplacement=easing(frame, begin, end-begin,totalframes);
    if(bezier) {
      actualDisplacement=QuadBezier(frame,actualDisplacement,end,bezier,totalframes);
    }
    setStyle(elm,prop,actualDisplacement/100;
}
function ColorStrategy(totalframes,frame,prop,props,easing) {
   var b = hexStr2rgbArray(props[prop].start);
   var e = hexStr2rgbArray(props[prop].end);
   var rgb=[];
   for(j=0;j<3;j++) {
        rgb.push(parseInt(easing(frame, b[j], e[j]-b[j], totalframes)));
   }
   setStyle(elm,prop,'rgb('+rgb.join(',')+')');
}
The numeric function will be in charge of working with numbers and therefore allow to handle many css properties : height, width, opacity, font-size,top,left...
We consider a number being a unique decimal number followed by its unity.
width:120px,
top:-100px,
opacity:.9
All these properties can be resumed to a unique number.
A margin or padding property is not considered a unique number but a property composed of serveral numbers, that the above function could handle individually.
The Color function is in charge of calculating the value for a string written in an hexadecimal way and defining a color.
This one is rather specific to one type of property but we can use it for color, background-color.
For now, there isn't that big changes, we have just put outside the calculation for each strategy, making the core function a little bit easier to understand but still unable to deal with margin or padding.
Before we go ahead, I shall show you how the strategy pattern does really look like as defined and how it will be used in practice in general:
var prop={
    color:{start:'#FFFFFF',end:'#0000FF'},
    'background-color':{start:'#FF0000',end:'#00FF00'}
};
animate(elm,prop,1500,25,easeInOutBounce,ColorStrategy);
var prop={
    width:{start:'150px',end:'20px'},
    top:{start:'0px',end:'-100px'},
    opacity:{start:0,end:1}
};
animate(elm,prop,1500,25,easeInOutBounce,NumericStrategy);
In that case, there won't be any switch of strategy in the function like we used to do at the beginning, the function will just apply the strategy given as a parameter in the function, like we did for the easing functions.
This is the usual basic way of implementing the strategy pattern but as you can see this is not very useful!
Now you can only animate one type of property at a time and you have to be aware of the strategy to apply...This is not what I will call a user friendly programming interface!
We need to keep the interface easy to use and hide all the process from the end user: the user shouldn't have to know that we change our strategy to calculate the property and the user should obviously not even know the existence of the 2 strategy functions!
In general, in a object oriented programming concept, this is what we call encapsulation or putting everything in a box that hides all the process to the end user.
In this regard, a function is the core, the basic tool you can use to create encapsulation!
instead of writing again and again the same calculation everywhere in your program, you put the calculation in a function, give it an easy to understand name and here you go, you encapsulate the process!
Our first approach, which was to switch the strategy in the function was a good way of keeping encapsulation.
So let's go back a little and keep the encapsulation by having the switching process between the strategies in the function, by using a simple if... else statement:
if(/[0-9]+/.test(parseInt(start)) && !/\s/.test(start)) {
 NumericStrategy(totalframes,frame,prop,props,easing);
} else {
 ColorStrategy(totalframes,frame,prop,props,easing);
}
We have seen that looking for a property containing the word color to switch between numeric or color strategy is not going to work with margin and padding.
Instead we are going to change our split strategy!
We will be looking for what we consider as being a numeric value and everything else! We first check to see if the parseInt value result in a number.
If parseInt can't find a number, it will send back the string,NaN, which stands for Not a Number.
But the margin will be considered as a numeric value so we need to look for any space in the string, if there is, we know that it's not something that our numeric function will be able to handle so we let the ColorStrategy does its works.
This is a little bit naive as it might in fact be the margin or padding or even worse, something we didn't think about!! and our function will crash...
But for now, let's go back to the end user and see how it works:
var prop={
    color:{start:'#FFFFFF',end:'#0000FF'},
    'background-color':{start:'#FF0000',end:'#00FF00'}
    width:{start:'150px',end:'20px'},
    top:{start:'0px',end:'-100px'},
    opacity:{start:0,end:1}
};
animate(elm,prop,1500,25,easeInOutBounce);
As you can see, this is a little bit easier for the end user!
It doesn't have to worry about the type of the property he/she used and therefore, keep the process very easy.
We have gain in the background an easier function to read and debug as we have put outside the calculation. let's see how the function looks like now:
function animate(elm,props, duration, fps,easing) {

      var duration    = (duration) ? parseFloat(duration) : 1000;
      var fps         = (fps)      ? parseFloat(fps)      : 20;
      var easing      = (easing)   ? easing               : easeOutBounce;
      var interval    = Math.ceil(1000/fps);
      var totalframes = Math.ceil(duration/interval);
      var Animator    = new Animate();

      for(var i=1;i <= totalframes;i++) {
         (function() {
     var frame=i;
            var setAnimation=function() {
     for(var prop in props){
                       var start=props[prop].start;
                       if(/[0-9]+/.test(parseInt(start)) && !/\s/.test(start)) {
                           NumericStrategy(totalframes,frame,prop,props,easing);
                       } else {
                           ColorStrategy(totalframes,frame,prop,props,easing);
                       }
     }
            }
            var timer = setTimeout(setAnimation,interval*frame);
        })();   
     }
}
As you can see, the function is shorter and as we have put outside the calculation into functions with easy to understand name, we can almost understand what's going on without having to think too much!
We can now change the numeric or color function without fearing to create bugs in the core function!
But if maintainability has been improved, our function is still unable to deal with margin or padding!
If we want to do so, we will need to go in the function, change our if... else statements, with all the problemes this could bring.
We should put this switch between strategy outside and use a function to wrap this, a function that will be in charge of building the right calculation, the right strategy, a kind of factory!

The factory pattern


You've been using the factory pattern all the time without even knowing it!
If we go back to the source and think of what is a factory, we could define it as something taking a raw element or several raw elements to output something that we can use.
When you write a function that changes an hexadecimal color string into an array of 3 numeric values, you are creating a factory: it takes a raw string that you can't use and change it into something usable and valuable for you!
In our case, the factory will be in charge of defining the proper strategy for the css element it receives and call it to have our final product in every case: a number to which we can apply a computation and therefore create an animation.
The definition of our strategy factory is very easy for now, we just wrap the if else statements in a function:
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);
     } else {
         ColorStrategy(totalframes,frame,prop,props,easing);
     }
}
This is one way of implementing the factory pattern in a procedural way, the basic and easy way.
Now, we will have to change the strategyFactory instead of the animate function, which is a little improvement but can we make things easier?
Of course we can!
Increasing the number of if... else statements, turning them into a switch statement to make it a little bit more readable won't change the fact that you will have to change the function everytime a new property you didn't think of appears.
If someone wants to add a new property, he/she will have to create a function and then go into the strategyFactory so that it can handle the new feature.
In short, nobody will try to extend your function!!

What will be a factory without a chart?


Every factories follow a chart : i need to have a tree with this property of this weight and length in order to create that desk. I need to use this particular stone in order to create a ring...
Here, our factory just required us to pass the proper arguments to the function to do its job and choose the right strategy
We are going to add a new chart!
The strategies should be named with the name of the css property they cover, changed in camelCase with the first letter in uppercase!
What does it mean?
if you want to implement the background-color css property, your function shall be called BackgroundColor, if you want to create a function that handles the margin property, it has to be called Margin...
By creating a naming convention and thus extending our chart, we will be able to have an intelligent factory able to call the required function without endless if... else statements!!
Let's see the function with the new chart:
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;
     }
     strategy=camelize(firstToUpperCase(prop));
     if(window[strategy]) { 
        window[strategy](totalframes,frame,prop,props,easing);
        return true;
     }
     return false;
}
function firstToUpperCase(val){
     return val.replace(/^([a-z])/,function(m,l) { return l.toUpperCase();})
}
???
You said there won't be any if else statements !!
No, in fact we are going to keep one and only one if statement in order to make things easier to develop! Let's see what's going on so that you understand why we keep this if:
The first part of the function is trying to determine if we are dealing with a unique number.
As you know, each css property should have a function that handles the calculation strategy but simple numerical css properties are numerous: top, left, width, height, opacity, border-radius,margin-top,margin-left,padding-top...
If we follow our new chart, we will have to create a function for each of them:
function Top(totalframes,frame,prop,props,easing) {
       NumericStrategy(totalframes,frame,prop,props,easing);
}
function Left(totalframes,frame,prop,props,easing) {
       NumericStrategy(totalframes,frame,prop,props,easing);
}
function Width(totalframes,frame,prop,props,easing) {
       NumericStrategy(totalframes,frame,prop,props,easing);
}
function Height(totalframes,frame,prop,props,easing) {
       NumericStrategy(totalframes,frame,prop,props,easing);
}
function MarginTop(totalframes,frame,prop,props,easing) {
       NumericStrategy(totalframes,frame,prop,props,easing);
}
I am not going to write them all but as you can see this a fair amount of wrapper functions to write. We can think of a unique numerical value as being the generality and everything else as being the exception, therefore this is what we do!
If we don't have a number, we change the css property in camelCase and then change the first letter into uppercase.
we need to call the function on the window object,that is the global space, holding all the functions and variables.
We then see if the function exists and apply it.
If the function doesn't exist we just retun false.
Now, we will be able to add new css properties without touching the factory and the core function!
It will be very easy for users to contribute to the function by just creating a function that has a name following the chart !
Now, we need to create the functions we are aware of that deal with one color formatted in hexadecimal:
function Color(totalframes,frame,prop,props,easing) {
   var b = hexStr2rgbArray(props[prop].start);
   var e = hexStr2rgbArray(props[prop].end);
   var rgb=[];
   for(j=0;j<3;j++) {
        rgb.push(parseInt(easing(frame, b[j], e[j]-b[j], totalframes)));
   }
   setStyle(elm,prop,'rgb('+rgb.join(',')+')');
}
function backgroundColor(totalframes,frame,prop,props,easing) {
      Color(totalframes,frame,prop,props,easing);
}
The little drawback is that we need to create a wrapper around the Color function for the background-color but we will be able to use the Color function in many other cases, we will see !

Conclusion


We have seen in this entry how we could create an expandable animation function that delegates strategies to outside functions.
We have seen how to use the strategy pattern associated with the factory pattern to get the most of it in a procedural way!
- The main function, animate is now smaller, which will create less overhead when using it
- The main function doesn't need to be aware of the css properties that exists as it has been abstracted in sub functions.
- Our animate function, abstracted of css properties, can now be extended as will by other users by following a simple naming convention. - we can load only the strategies we need for an animation if we want! (if you don't animate colors, don't import the color strategy!) - the animate function can be extended with more elements like events without fearing to create a monster function!
We have seen how we can use the concepts behind design patterns generally used for object oriented programming in a procedural manner but we have several problems with the procedural approach:
- All our functions is in the global name space, the window name space.Therefore, conflicts with other libraries that could use the same naming conventions could appear and create hard to track bugs.
- we can't share a state between functions easily We will see in the next entry how we can simulate namespaces in javascript thru objects and how we can extend the number of css properties our function can handle with Margin and Padding functions !

Sunday, October 21, 2007

Javascript Animation : Curving the path (7)

We've seen that we could define the core concept of an animation thru a linear equation that took basic elements like time, begin, end, change.
We have changed the equation to create some easing effects, making the animation a lot more interesting.
But there is one thing that we cannot do : describe a curve when going from point A to point B.
Let's see how to implement a curved path!

Curve by example


I guess it's easier to understand what we are talking about if we have a grasp of the final result.
First, this is what we are able to do know:
We can go from y0,x0 to y1,x1 or from top0, left0 to top1, left1.
Even if we use an easing effect, going from point A to B will describe a straight line:

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Go up
As you see, the easing effect is applied and the container goes from point A to B in a straight line.
Here is the code to create the effect:
function goUp() {
 elm=document.getElementById('goup');
 var ops1={
   backgroundColor:{start:'#000000',end:'#FF0000'},
   borderColor:{end:'#000000',start:'#FF0000'},
   top:{start:0,end:-100},
   left:{start:0,end:250}
 };
 animate(elm,ops1,1500,20,easeInCirc);
}
So we get the element,set the css properties we want to animate and then specify an animation of 1 second and a half with an easeInCirc effect.

That's find, but it could be nice if we could control the path going from top.0,left.0 to top.-100,left.250.
We can set an in-between point that will 'attract' the animation.
we have 3 divs/square on the screen here:
The beginning square,
The attracting square,
The final square
You can set different value for the attracting point and see how the animation will behave:

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Attraction Point:
top:
left:
animate with curve

As you can see, the animation will not go straight to the final point but will describe a curve according to the attracting point you have set!
We can now have little control on the path!
But let's see how it works.

Going back to the root : y=mx+b


As you certainly remember, we've seen that a linear displacement from one value to an other could be resumed to one equation:
value =(change/duration)*frame + start
or
y=mx+b
Where m is (change/duration) or c/d

In fact, we can rewrite this linear equation so that it becomes the root of an other equation : the Bezier equation.
Let's rewrite it:
value = begin+(begin+change-begin)*(frame/totalframes)
If you look at the changement, you will see that we do we take a deturned path:
change = begin+change-begin
Which is not very helping in here!
In fact, this is the basic equation for a linear Bezier curve!
We can rewrite it like so:
value = begin+(end-begin)*(frame/totalframes)
or
var t=frame/totalframes
var b=begin
value=begin+(end-begin)*t

In the Bezier curve, t must always be within 0 and 1 and this will always be true for how frame/totalframes.
The upper equation can be rewritten again:
value = (1-t)*begin + t*end

This is an other way of creating a linear easing!

From linear to Quadratic Bezier curve


As for now, we were just rewritting a linear equation to an other linear equation!
In fact, we can add an other point, like we did in the animation above and this will allow us to curve the path.
We will need p0,p1 and p2 where p1 is the deviator point:
value= (1-t)*(1-t)*p0 + 2*t*(1-t)*p1 + t*t*p2
In our case, with javascript variable name, this will give us:
var t=frame/totalframes;
value = (1-t)*(1-t)*begin + 2*t*(1-t)*deviator + t*t*end;

Let's see the Bezier curve with a javascript implementation:
function linearBezier(frame,begin,end,totalframes) {
      var t=frame/totalframes;
      return (1-t)*begin + t*end;
}
function QuadBezier(frame,begin,end,deviator,totalframes) {
    var t=frame/totalframes;
    return (1-t)*(1-t)*begin + 2*t*(1-t)*deviator + t*t*end;
}

Implementing the Bezier Curve


Now that we know how to curve the path thru a Bezier curve, we just need to see how we can implement this in our animate function.
First, we need to add a third point, a deviator point when setting the values.
A simple way of doing it,will be to set the deviators like so:
 var ops1={
     backgroundColor:{start:'#000000',end:'#FF0000'},
     borderColor:{end:'#000000',start:'#FF0000'},
     top:{start:0,end:-100,bezier:-300},
     left:{start:0,end:450,bezier:-100}
 };
 animate(elm,ops1,1500,20,easeInCirc);
I have written it bezier because this is a common denomination that you can find in photoshop, illustrator, Flash...
The Bezier curve is used in many graphic applications so let's use a standard name!
Now, let's add this bezier into the animate function:
function animate(elm,props, duration, fps,easing) {

 duration = (duration) ? parseFloat(duration) : 1000;
 fps      = (fps)      ? parseFloat(fps)      : 20;
 easing   = (easing)   ? easing               : linearEase;

 var interval    = Math.ceil(1000/fps);
 var totalframes = Math.ceil(duration/interval);

 for(i=1;i <= totalframes;i++) {
   (function() {
      var frame=i;
      displacement=function() {
          for(var prop in props){
              if(!/olor/.test(prop)) {
                  var begin = props[prop].start*100;
                  var end   = props[prop].end*100;
    var bezier = props[prop].bezier*100;
    var actualDisplacement=
                      easing(frame, begin, end-begin, totalframes);
    if(bezier) 
                    actualDisplacement=
                    QuadBezier(frame,actualDisplacement,end,bezier,totalframes);
           setStyle(elm,prop,actualDisplacement/100;
               } else {
                    var b = hexStr2rgbArray(props[prop].start);
                    var e = hexStr2rgbArray(props[prop].end);
      var rgb=[];
                    for(j=0;j<3;j++) 
                    rgb.push(parseInt(easing(frame, b[j], e[j]-b[j], totalframes)));
                    setStyle(elm,prop,'rgb('+rgb.join(',')+')');  
        }
   }
       }
       timer = setTimeout(generalProperty,interval*frame);
   })();   
 }
}
So what do we do?
We first calculate the actual value of the property according to the easing type.
If we find that a bezier property exists,we use the value calculated by the easing type as the start for the bezier equation !
That's all!

A new effect thru Bezier : backAndForth


In the above example, we need to supply a value for the bezier curve to happen, but we can always calculate the bezier point by manipulating the beging and end value!
Let' see the code:
function backAndForth(frame,begin,change,totalframes) {
 var t=frame/totalframes;
 var end=begin+change;
 return (1-t)*(1-t)*begin + 2*t*(1-t)*(end+change) + t*t*begin;
}
As we are using the backAndForth function as an easing effect, we are not getting the end directly but the change so we need to get back the change.
Here I've decided that the deviator will be the end+change and that the final point will be the beginning!
Let's see what we get with the following settings:
 var ops1={
     backgroundColor:{start:'#000000',end:'#FF0000'},
     borderColor:{end:'#000000',start:'#FF0000'},
     top:{start:0,end:-100},
     left:{start:0,end:450},
     borderWidth:{start:5,end:1}
 };

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
back and forth
As you can see, the animation goes to the end value and goes back to where it started!
This is not really an easing effect but more an effect in itself but this can be useful!

What happens if we mix the backAndForth easing with some bezier points??
Until up to now, we've been applying the bezier points only on the top or left property but can we apply the bezier points to other properties?
Let's try, with the following settings:
 var ops={
     backgroundColor:{start:'#000000',end:'#FF0000'},
     borderColor:{end:'#000000',start:'#FF0000'},
     top:{start:0,end:-100,bezier:-300},
     left:{start:0,end:450,bezier:200},
     borderWidth:{start:5,end:1,bezier:80}
 };
 animate(elm,ops,1500,20,backAndForth);
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Go back and forth with bezier
Well, this is not going back and forth anymore!
The bezier equation is last in the function so it will reach the final destination and the backAndForth will create a kind of easing effect.
If we inverse the order in which bezier/easing is calculated, we will get an other effect...
We notice that the value set in the border-width can be used as a simple intermediate value for the animation too!

Conclusion

We have seen a basic way of implementing the a quadratic bezier curve in our animation process and we have seen that playing around with the value used in the bezier can allow some unexpected effects!
But as always, drawbacks are numerous:
- we can set only one bezier point per property, most of the time cubic bezier is used, which allows to set up 2 points per property. We could even find a way to set as many as bezier points as we like!
- we cannot add a bezier points to the colors as this will require a different calculation.
- we modified the animation directly which could lead to some weird bugs if we were to add more features (several beziers, beziers to colors...).
The core code is starting to be huge and it will certainly be better to find a way to improve some functionalities of the animate function thru callbacks for example.
Therefore we could keep the basic function simple in its process and allow us to extend its possibilities without fearing to create bugs or to create a monster function!
After all, we may not need the bezier possibilities so why put it in the core of the function?
Obviously I have specified other drawbacks in the previous entry and they still hold true!
But anyway, enjoy the function as for now and try to create your own effects based on all the tools you have!

Sunday, October 14, 2007

Javascript Animation : Several elements at once (6)

Until up to now, we have seen how to manipulate one css property of an element at a time and few css styles. I guess it is time to see how to manipulate several css properties in one animation!

Some examples


Before we dive into the code, we are going to see some of the effects you will be able to create.
These effects only include the linear easing, which means a fix step, but you could apply the easeInOutQuad, CircularOut, Bounce...
You can use the function in many different ways and these are just few ideas:
(Ideas inspired by the mootools demo page)

Slide Horizontal

Slide In Slide Out
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

Slide Vertical

Slide In Slide Out
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

Fade

Fade In Fade Out
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

Morph

Morph to Morph back
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.


Manipulating several elements

Until up to now, we were manipulating only one css property at a time, it could be the width, the height, the opacity but not all of them at once!
In order to define several properties at once, we are going to use a literal object.(You can read a simple introduction about this here:Javascript literal notation:basics)
As a reminder this is how we used to call the function:
animate(elm,property,start,end,duration,fps,easingType);
You can read the previous articles of the series if necessary.
As we are going to use a literal object to define the properties, this is how we will call the function:
animate(elm,properties,duration,fps,easingType);
We could define different ways to define the css properties:
var properties={
   color:['#FFFFFF','#00FF00'],
  'background-color':['#000000','#ffffff'],
   opacity:[1,.5],
   width:[200,400]
}
Here we create a literal object and then define the start and end value in an array literal for each css properties.
This is fine but this is not very clear and if we were to add some functionalities, this might get cumbersome to remember the order, therefore, we will write it this way:
var properties={
   color:{start:'#FFFFFF',end:'#00FF00'},
  'background-color':{start'#000000',end:'#ffffff'},
   opacity:{start:1,end:.5},
   width:{start:200,end:400}
}
Instead of defining the beginning and the end of the animation thru a literal array, we use a literal object.
This is a little bit longer to write but clearer and allow to add further elements like callbacks, type of easing per property basis if we want!
As for the css properties, the colors and some cross-browsers implementation, you can read the following entries:
Javascript Animation: Changing Colors (5)
And
Setting html element style thru Javascript

We will need to use some of the functions explained in these entries so feel free to read these if you don't get it.

The animation code


Let's see the main function : animate!
function animate(elm,props, duration, fps,easing) {

 duration = (duration) ? parseFloat(duration) : 1000;
 fps      = (fps)      ? parseFloat(fps)      : 20;
 easing   = (easing)   ? easing               : linearEase;

 var interval    = Math.ceil(1000/fps);
 var totalframes = Math.ceil(duration/interval);

 for(i=1;i <= totalframes;i++) {
  (function() {
    var frame=i;
    interpolate=function() {
      for(var prop in props){
        if(!/olor/.test(prop)) {
          var begin = props[prop].start*100;
          var end   = props[prop].end*100;
          setStyle(elm,prop,
                   easing(frame, begin, end-begin, totalframes)/100);
        } 
        else {
          var b = hexStr2rgbArray(props[prop].start);
          var e = hexStr2rgbArray(props[prop].end);
          var rgb=[];
          for(j=0;j<3;j++) 
              rgb.push(parseInt(
                       easing(frame, b[j], e[j]-b[j], totalframes)));
          setStyle(elm,prop,'rgb('+rgb.join(',')+')');  
        }
      }
    }
    timer = setTimeout(interpolate,interval*frame);
   })();   
  }
}
There's not that much new:
First we define some default values for the duration, fps and easing.
Then we calculate the total number of frames and loop thru them.
We loop thru each properties in the object and set the style to the element.
We need to separate the colors from other properties as we need to calculate 3 values at a time!
We apply the easing function passed in the function.It could be Bouncing, Quad, Quart...All of them calculating the evolution from start to the end in a non-fix manner!
That's all!

Helper functions


You have certainly noticed that the function use other functions to set the style, calculate the colors.
Here is a list of all the helper functions:
function d2h(dec) { 
  return dec.toString(16);
}
function h2d(hex) { 
   return parseInt(hex,16);
}
function rgb2h(r,g,b) { 
    return [d2h(r),d2h(g),d2h(b)];
}
function h2rgb(h,e,x) {  
     return [h2d(h),h2d(e),h2d(x)];
}
function cssColor2rgb(color) {
     if(color.indexOf('rgb')<=-1) {
       return hexStr2rgbArray(color);
     }
 return rgbStr2rgbArray(color);
}
function hexStr2rgbArray(color) {
 return h2rgb(color.substring(1,3),color.substring(3,5),color.substring(5,7));
}
function rgbStr2rgbArray(color) {
 return color.substring(4,color.length-1).split(',');
}
function camelize(val) {
   return val.replace(/-(.)/g, function(m, l){return l.toUpperCase()});
}
function setOpacity(elm,val) {
    elm.style.zoom = 1;
    elm.style.filter = "alpha(opacity=" + parseFloat(val*100) + ")";
    elm.style.opacity  = parseFloat(val); 
    return elm;
}

function setStyle(elm,prop,val) {
  if(prop=='opacity') 
     return setOpacity(elm,parseFloat(val));
  if(prop=='float')   
     prop = (window.attachEvent) ? 'styleFloat' : 'cssFloat';
  prop = camelize(prop);
  unit=(prop=='zIndex'||prop=='zoom') ? '':'px';
  elm.style[prop] = (typeof val=='string') ? val : val+unit;
  return elm;
}

function linearEase(frame,begin,change,totalframes) {
      return change*(frame/totalframes)+begin;
}
Some of them can be reused alone like the setStyle, setOpacity or even the camelize...
I don't put all the easing functions as you can find them in the past entry:
Javascript Animation: Make things move smoothly

Conclusion


The function allows us to do some nice effects, controlling the time, the number of frames per second, the type of easing and now we can manipulate several properties at a time in a cross-browser way!
But as always, the drawbacks are numerous:
- They are some css properties that need to be written in multiple stage to work like the border or margin properties.
- We can apply only one type of easing for all the properties in one animation but sometimes you will like a linear easing (color) combined with a bouncing easing (top,left properties).
- We didn't define a way to have callbacks and chain effects one after an other.
- If you click several times on a link, the animation will just go out of control! We need to keep somewhere the state of the animation!
- We need to write all the css properties, which can be cumbersome quickly and we don't keep the DRY principle (Don't Repeat Yourself!) as you will certainly define a css class and then rewrite some of the elements in the animation! From there, we can think that this could be nice to be able to set the animation from one class to an other!
- If you want to move your element from top0,left0 to top1,left1, this will be a straight line, even if you apply an easing function, you won't be able to curve the path...Perhaps, we could use a Bezier as we may already have the ingredients! The list is still long so I will stop for now but have fun!

Sunday, October 7, 2007

Javascript Animation : Changing Colors (5)

We have seen so far how to manipulate different properties of a div such as the width, height, top, left and opacity. We also know how to apply easing to the interpolation in order not to have a mechanical animation.
Well, I guess it is time to expand the properties we can deal with and see how to animate colors !

The animation


Starting color:
End color:
test

Colors ?


What a better start than a refresher about colors ?
This may sound too much but let's see quickly some basic things about them as this will help us understand some programmatic points.
(Obviously, books exist only about colors but we will just see what is relevant for us in that case)
As you may know all the colors human can see is a combination of 3 primary colors.
The 3 primary colors are arbitrary colors that respond to some particular needs.
In art, the 3 primary colors are red, yellow and blue.
Electronic displays use for the most, red green and blue.
In printing industry, cyan, magenta and yellow.
The mixing of one set of primary colors allow to recreate all the colors you may need.
Computer displays use the red, green and blue primary colors, abbreviated to rgb.
So do the browsers!

RGB Notation


These 3 colors can be set different values, allowing different flavor of each : red or light red or light light red... for example.
Actual displays set 8 bits for each colors per pixel.
For reference' sake, 8 bits or one byte is something like : 01001100.
one bit can be set to 0 or 1, which means 2 possible value.
Therefore, one color can have 28 values, 256 values or 255(starting from 0).

The RGB notation used the numerical value within 0 to 255 to define the value of one color, thus we get:

red:0 to 255
green:0 to 255
blue:0 to 255

As an example, if you set red as being 255 and green, blue as being 0, you get red
If you set red as 0, green as 255 and blue as 0, you will get... green !

But why I am talking about this ?

Because browsers deal internally with colors by using the RGB Notation like this:

rgb(255,112,220);

And this will help us a lot as we know how to interpolate and calculate numbers!

The HEXADECIMAL NOTATION


As you can see the rgb notation will certainly be helpful but have you ever written such a color when setting some style to your xhtml thru CSS ?

You have certainly written something like:
color:#FF0000;
which prints out the text in red!

This notation use the hexadecimal version of the rgb notation!
...
The hexadecimal counts from 0 to F (base 16) instead of 0 to 9 in decimal (base 10).
So A is equal to 10, B to 11, C to 12...
1A is 1*16+10=26
FF is 15*16+15=255

You won't really have to count using the hexadecimal base.
The hexadecimal system is used to set the 3 colors in a shorter way and easier way to remember, that's all we need to know!
So, #FF1AF1 is setting red as FF, green as 1A and blue as F1 which in decimal notation will be red:255,green:26 and blue:241!

Browsers and Colors


So we set the colors in hexadecimal but we need the rgb decimal numbers to animate the color!

In Firefox there won't be any problems because if you set the color in hexadecimal and query it thru javascript, Firefox will send you back the rgb notation!
But IE will send you back the hexadecimal value!
So we will need to convert the hexadecimal value back to a rgb notation!
document.getElementById('square').style.color='#FFFFFF';
alert(document.getElementById('square').style.color);
//In ie return #FFFFFF
//In Firefox return rgb(255,255,255)

Converting notations in Javascript


Well, we are lucky because javascript already has the function we need!
function d2h(dec) { 
       return dec.toString(16);
}
function h2d(hex) { 
       return parseInt(hex,16);
}
We are just wrapping them into two functions to short it up!
The d2h (decimal to hexadecimal) takes a decimal value and will send back the hexadecimal value.
alert(d2h(255));//alert FF
The h2d (hexadecimal to decimal) takes an hexadecimal value and will send back the decimal value.
alert(d2h('FF'));//alert 255
From there we can build 2 functions that will convert rgb and hexadecimal notation:
function rgb2h(r,g,b) { 
         return [d2h(r),d2h(g),d2h(b)];
}
function h2rgb(h,e,x) {
        return [h2d(h),h2d(e),h2d(x)];
}
These two functions send back an array of each value.
If you are not familiar with the [] notation, just know that it creates an array.
I'll write about this later I guess

We can use them like so:
rgb2h(255,255,255);// return [FF,FF,FF]
h2rgb('FF','FF','FF');// return [255,255,255]
Now it's time to create a cross-browser function that will allow us to get back an array of value of each rgb value:
function cssColor2rgb(color) {
     if(color.indexOf('rgb')<=-1) {
     return h2rgb(color.substring(1,3),color.substring(3,5),color.substring(5,7));
     }
     return color.substring(4,color.length-1).split(',');
}
//example:
cssColor2rgb('#FFFFFF'); return [255,255,255]
cssColor2rgb('rgb(255,255,255)'); return [255,255,255]

First we check if we do not have an hexadecimal value thru indexOf that will return the position of the search in the string if found or -1 if not found.
Then if the function didn't find the rgb string, it means that we are dealing with an hexadecimal notation so we send each value to the h2rgb thru the substring function:
color.substring(1,3) is FF as 0,3 is '#FF'
Then return
(By the way we check if rgb doesn't exist instead of looking for # because browsers start counting the value found from 0 or 1 but -1 means everywhere that it wasn't found)
Otherwise, we are in front of a string 'rgb(value,value,value)'.
The 'rgb(' and ')' are not necessary so we just get rid of them and split the string by comma returning an array.
So now we have solved the cross browser problem, let's see how to incorporate this in the animation!

The Color Animation Code


In order to simplify the code, we are going to see how to animate only the color and not other properties.
We are stepping back but this is to jump higher!
function animateColor(elm,begin,end, duration, fps) {

  if(!duration) duration = 1000;
  if(!fps) fps = 20;
  duration=parseFloat(duration);
  fps=parseFloat(fps);
  var interval    = Math.ceil(1000/fps);
  var totalframes = Math.ceil(duration/interval);

  for(i=1;i <= totalframes;i++) {
                 (function() {
                   var frame=i;
                   var b = cssColor2rgb(begin);
                   var e  = cssColor2rgb(end);
                   var change0=e[0]-b[0];
                   var change1=e[1]-b[1];
                   var change2=e[2]-b[2];

                  function color() {
                  var increase0=ease(frame, b[0], change0, totalframes);
                  var increase1=ease(frame, b[1], change1, totalframes);
                  var increase2=ease(frame, b[2], change2, totalframes);

    elm.style['backgroundColor']  = 'rgb('+parseInt(increase0)+','+parseInt(increase1)+','+parseInt(increase2)+')';        
                  }
                  timer = setTimeout(color,interval*frame);
                 })(); 
 }
}
function ease(frame,begin,change,totalframes) {
       return begin+change*(frame/totalframes);
}
//example:
animateColor(document.getElementById('squareColor'),'#ff0000','#0000ff',3000,20);
Well, this function is almost like the one we have seen so far!
We have simplify as we do not set the property we are animating for now.
Then we change the hexadecimal values to rgb notation, calculate a linear step for each rgb value and pass the integer value (parseInt)to the property!
That's all!

The hardest thing was to deal with the switching from one notation to an other!
The main function in itself doesn't change that much even if, for now, we have created a new function dedicated to color animation.
We will soon see how to reincorporate this into the main function animate!

Conclusion


We do know now how to animate colors and I guess this can be very useful but as always, there are many drawbacks;
- we have created a function to animate only the background color of the element!
We should be able to animate all the properties with only one function to allow complex animations
- we still need to add some properties to our animation tool box!
- we used the linear easing here, even if we have several easing flavors with us.
Could we create some new way of animating the element thru equations ?
I guess so...

Thursday, October 4, 2007

Javascript Animation : Make things move smoothly (4)

We have seen so far many ways to move a div property : you know how to make its width, height, top, left, opacity change over a chosen time and a number of frames. You also know the basic of easing and how it works, everything starts from one linear equation : y=mx+b.
Thanks to this, we are know able to create bunches of easing effects.
Let's explore them !

Easing by example : the square was the source


Let's start by a little animation that can handle what we have seen so far and some cool easing.
Obviously, they are many things we can improve and we will !



Cool Text
reset
Normal motion
Normal motion high quality
Normal motion low quality
Slow motion
Fast motion
Fade In
Fade Out
SlideIn
SlideOut


Easing equations


I hope you enjoyed this little basic effects because we will see how to do even more soon!
If you remember the last entry about easing, we have seen that everything was in relation with some core elements:
- object to animate
- start
- end - duration

From there we could built some other elements:

- object's property (we shall say properties soon!)
- frame per second or fps
- change in animation being the end-start

All these elements are linked thru one basic liner equation:
property value =(change/duration)*frame + start
or
y=mx+b
Where m is (change/duration) or c/d

We know that this is the shortest path to go from the start to the end as this equation will draw a line but we also know that life is not a straight line but something curved, changing all the time !

We tried to change the equation to something else by keeping the proportions of the equation, which means that if we had somewhere, we must remove somewhere else.

y=m*x*(x/d)+b

This is not a straight line but a curved line and that correspond to a quadratic easing !
If you multiply again by (x/d), it will be quintic...
We could create our own functions and I'm sure you will but let's for now, use the equations from Robert Penner from whom I have learn a lot !
He has spent a lot of time finding some cool equations creating effects, they are all based upon the following components:
- frame (time or t)
- change (c)
- start (begin or b)
- duration (d)

As it's not about reinventing the wheel all the time, we will use them!
I have kept Robert Penner abbreviation as you will certainly find them in many javascript frameworks like mootools, jQuery and the like !
So here we go!
easeInQuad = function (t, b, c, d) {
 return c*(t/=d)*t + b;
};
easeOutQuad = function (t, b, c, d) {
 return -c *(t/=d)*(t-2) + b;
};
easeInOutQuad = function (t, b, c, d) {
 if ((t/=d/2) < 1) return c/2*t*t + b;
 return -c/2 * ((--t)*(t-2) - 1) + b;
};
easeInCubic = function (t, b, c, d) {
 return c*(t/=d)*t*t + b;
};
easeOutCubic = function (t, b, c, d) {
 return c*((t=t/d-1)*t*t + 1) + b;
};
easeInOutCubic = function (t, b, c, d) {
 if ((t/=d/2) < 1) return c/2*t*t*t + b;
 return c/2*((t-=2)*t*t + 2) + b;
};
easeInQuart = function (t, b, c, d) {
 return c*(t/=d)*t*t*t + b;
};
easeOutQuart = function (t, b, c, d) {
 return -c * ((t=t/d-1)*t*t*t - 1) + b;
};
easeInOutQuart = function (t, b, c, d) {
 if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
 return -c/2 * ((t-=2)*t*t*t - 2) + b;
};
easeInQuint = function (t, b, c, d) {
 return c*(t/=d)*t*t*t*t + b;
};
easeOutQuint = function (t, b, c, d) {
 return c*((t=t/d-1)*t*t*t*t + 1) + b;
};
easeInOutQuint = function (t, b, c, d) {
 if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
 return c/2*((t-=2)*t*t*t*t + 2) + b;
};
easeInSine = function (t, b, c, d) {
 return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
};
easeOutSine = function (t, b, c, d) {
 return c * Math.sin(t/d * (Math.PI/2)) + b;
};
easeInOutSine = function (t, b, c, d) {
 return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
};
easeInExpo = function (t, b, c, d) {
 return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
};
easeOutExpo = function (t, b, c, d) {
 return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
};
easeInOutExpo = function (t, b, c, d) {
 if (t==0) return b;
 if (t==d) return b+c;
 if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
 return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
};
easeInCirc = function (t, b, c, d) {
 return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
};
easeOutCirc = function (t, b, c, d) {
 return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
};
easeInOutCirc = function (t, b, c, d) {
 if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
 return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
};
//a: amplitude (optional), p: period (optional)
easeInElastic = function (t, b, c, d, a, p) {
 if (t==0) { return b; } 
 if ((t/=d)==1) { return b+c; }
 if (!p) { p=d*.3; }
 if (a < Math.abs(c)) {  a=c; s=p/4; }
 else { a=Math.abs(c); s = p/(2*Math.PI) * Math.asin(c/a);}
 return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
};
easeOutElastic = function (t, b, c, d, a, p) {
 if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
 if (a < Math.abs(c)) { a=c; var s=p/4; }
 else {   a=Math.abs(c); var s = p/(2*Math.PI) * Math.asin (c/a);}
 return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b;
};
easeInOutElastic = function (t, b, c, d, a, p) {
 if (t==0) return b;  
 if ((t/=d/2)==2) return b+c;  
 if (!p) p=d*(.3*1.5);
 if (a < Math.abs(c)) { a=c; var s=p/4; }
 else {a=Math.abs(c);var s = p/(2*Math.PI) * Math.asin (c/a);}
 if (t < 1) {return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;}
 return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
};
easeInBack = function (t, b, c, d, s) {
 if (s == undefined) s = 1.70158;
 return c*(t/=d)*t*((s+1)*t - s) + b;
};
easeOutBack = function (t, b, c, d, s) {
 if (s == undefined) s = 1.70158;
 return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
};
easeInOutBack = function (t, b, c, d, s) {
 if (s == undefined) s = 1.70158; 
 if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
 return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
};
easeInBounce = function (t, b, c, d) {
 return c - easeOutBounce (d-t, 0, c, d) + b;
};
easeOutBounce = function (t, b, c, d) {
 if ((t/=d) < (1/2.75)) { return c*(7.5625*t*t) + b;} 
        else if (t < (2/2.75)) { return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;} 
        else if (t < (2.5/2.75)) { return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;}
        else { return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b; }
};
easeInOutBounce = function (t, b, c, d) {
 if (t < d/2) return easeInBounce (t*2, 0, c, d) * .5 + b;
 return easeOutBounce (t*2-d, 0, c, d) * .5 + c*.5 + b;
};

Wow, this was huge!
Finding evolutions to the animation thru equations might not be as easy but it gives you full control of what you want to do!
Remember that all these functions start from only one equation:
y=mx+b
So don't fear to try yourself!
3D games and much complexed animations are all based upon equations related to physics.
Why not had a friction coefficient ? an hypothetic wind ? some random evolutions too ?

Conclusion


I guess the animation function is starting to be interesting but as always, there are still many drawbacks:
- You can not animate several properties of an element! It's one property at a time.
- We do not cover all the properties possible like colors, borders and a bunch of other css properties!