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!

1 comment:

Anonymous said...

You have helped me immensely.
I much prefer building things from the ground up and understanding what the hell is going on, instead of sifting thru some enormous library of someone else's design.
You have brightened my holiday season.Thank you