Flarnie Marchán

Hello! I'm a software engineer with a passion for the web.

Raphael JS Animation: Color Transitions

Published Fri Jan 04 2013 00:00:00 GMT-0800 (PST)
This is another example of animation with Raphael JS, the JavaScript library for generating and animating cross-browser vector graphics. Animating a transition between two colors is easy, but what if you want the animation to loop? And what about multiple colors? This function takes a list of colors as input and animates the target element. Optionally, the timing and transition style of the animation can be specified, otherwise they are set to a default of 2 seconds and "linear". (The Raphael JS homepage has a demo of the available animation, or "easing" styles here.) When working with Raphael JS animations, this function could be used as a template for other types of looped animation. Read on for an explanation of the code.

Looping JavaScript Animation with Raphael JS

The key is to set the initial value of the attribute, and then to pass the name of the function again as the last argument of Element.animate(). The author of the Raphael JS library provides a concise example in response to a post on stackoverflow:
var starSpin = function () {
    logoStar.attr({rotation: 0}).animate({rotation: 360}, 5000, starSpin);

//Animation from 360° to 360° looks like there no animation, 
//so you need to reset rotation to zero before.

Multiple Animations in a Que

To create a looping sequence of animations requires a sort of nesting of functions within Element.animate(). In the example, the background element transitions through three values for the "fill" attribute. Simply writing a function with three uses of Element.animate() will not work- only the last call to Element.animate() will be executed.

The Hard Way

Instead, each animation must finish by calling the next animation, with the final animation restarting the sequence.
var colorchange = function(e) {

var third = function(){
    e.attr({fill:"red"}).animate({fill:"blue"}, 5000, 
function(){colorchange(e)}); }

var second = function(){
    e.attr({fill:"yellow"}).animate({fill:"red"}, 5000, third) }

var first = function(){
    e.attr({fill:"blue"}).animate({fill:"yellow"}, 5000, second); }


In the above example, three functions are defined: 'first', 'second', and 'third'. The 'first' function specifies a call to the 'second' after it's animation, and the 'second' specifies a call to the 'third' after it's animation. The 'third' and final function calls the containing function, starting the whole sequence again. After defining these three functions, we only need call 'first' to set them all in motion.

The Easy Way

The previous example makes it clear what is happening, but would be cumbersome for more than a few colors. By putting the attribute values for each animation in an array, the entire chain is condensed into one function:
//the 'colors' argument is an array of colors
var exampleColorList = ['red', 'blue', 'yellow'];

//The above array will be given as an argument to the following function
var colorchange = function (colors, target, seconds=2, style='linear') { 
//grab the first color from the list
var firstcolor = colors.shift();

//create a new color array with the old 'first' color at the end
var newcolors = [firstcolor]
newcolors = colors.concat(newcolors);

//add the 'first' color back to the original 
colors = [firstcolor].concat(colors);

//Transition between the first two colors in the list, 
//and then restart with the new color array 
//(which has the first color removed and stuck on the end.)
    target.attr({fill:colors[0]}).animate({fill:colors[1]}, seconds*1000, style, function(){colorchange(newcolors, target, seconds, style)});

Avoiding an Overflow or Infinite Loop

If your animation seems jumpy, doesn't work, or you get pop-ups from the browser that read "A script on this page may be busy, or it may have stopped responding. You can stop the script now, or you can continue to see if the script will complete." then you have made a small and subtle error that is causing an out of control loop in your script. A possible cause is calling a function in the Element.animate() instead of simply providing the function to be called. WRONG:
Element.animate(… colorchange(newcolors, target, seconds, style););
Element.animate(...function(){colorchange(newcolors, target, seconds, style)} );