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!

No comments: