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...

8 comments:

Anonymous said...

Thanks, really

Anonymous said...

Thanks a lot, I was looking for something like that to interpolate between two colors

AHHP said...

All javascript effects are about JS libraries and it's really hard to find some great articles like this.
Thank you!

madeinqc said...

thanks you so much ! :)
I actually can't use Animation library since my project have to run in a lobo web browser so I have to create my own library ;P

your tutorial just helped me to make the color transition part :)

now I need to figure out how to make smooth animation from one class to an other :o

thx again ^^

shiriru said...

Thank you all for your encouraging comments!

You might want to have a look at the articles written so far as they explain how to create easing functions too.
You have a sum up of all the articles here:

Understand JS and effect library in 10 parts

Hope this can be helpful

Anonymous said...

If I quickly click "test" two times or more, then the color become very strange.

shiriru said...

>If I quickly click "test" two times or more, then the color become very strange.

If you click one more time while the animation is running, an other one will start and interact with the same element, conflicting with the first animation.

No throttling is being undertaken to avoid running the animation twice against the same element.

You could avoid this:

- restart the animation from start
- merge the value together and the second animation takes over.
- wait for the first animation to finish before fire the other one
- cancel any new attempt to animate the element if it is already running.

But this would be quite heavy to detail in this tutorial which was mainly about manipulating the colors.

Christian said...

Wonderfull script, THANKS A LOT
One detail to correct is when you use rgb(x,y,z) for the begin color. String concatenation happend instead of sum.
So correct this function:
function ease(frame,begin,change,totalframes) {
return parseInt(begin)+change*(frame/totalframes);
}