JavaScript animations are a key aspect of dynamic Web Sites and Application development. Moreover, most JavaScript Frameworks or Libraries provide APIs for dealing with at least three main things:
- Advanced DOM manipulation
- Ajax
- Animations
When developing Web Sites most JavaScript effects involve rendered DOM Elements, but sometimes JavaScript animations are used in other contexts, like when using the Canvas. In the JavaScript InfoVis Toolkit the main target of my animations are Graphs, and in the next version also Nodes and Edges as separate entities.
Today I’d like to describe how to create a generic animation class that can be used or extended for any purpose. I’ll try to be minimalistic and to present only the needed code for making animations. Then you might find useful to add some code to perform specific animation tasks targeting for example specific style properties of a DOM Element.
Defining an Animation Class
Before creating an Animation class we might want to consider what to expose as options to the user. The options I thought of are:
- The duration of the animation (in milliseconds)
- The frames per second of the animation
Additionally we’d like to add a couple of controllers, one when a step of the animation is executed and one when the animation has completed:
var options = {
duration: 1000,
fps: 40,
onStep: function(delta) {},
onComplete: function() {}
};
delta gives us an idea of the progress of the animation. When the animation starts delta will be equal to zero. When the animation ends it’ll be equal to one.
We will also need a start method to trigger the animation and a step method that will compute delta at each step.
Now that we defined our options we can start thinking about our implementation.
Implementing the Animation class
Our Animation class will be a class constructor that sets all the options and properties that we defined before and a prototype with the methods start and step. The class could be used like this:
var fx = new Effect({
duration: 1000,
fps: 40,
onStep: function(delta) {
/* do stuff */
},
onComplete: function() {
alert('done!');
}
});
//start the animation
fx.start();
Here’s the code I came up with, inspired by the MooTools Framework:
//define the class constructor
function Effect(opt) {
this.opt = {
duration: opt.duration || 1000,
fps: opt.fps || 40,
onStep: opt.onStep || function(){},
onComplete: opt.onComplete || function(){}
};
}
Effect.prototype = {
//define how the animation starts
start: function() {
//return if we're currently performing an animation
if(this.timer) return;
//trigger the animation
var that = this, fps = this.opt.fps;
this.time = +new Date;
this.timer = setInterval(function() { that.step(); },
Math.round(1000/fps));
},
//triggered at each interval step
step: function() {
var currentTime = +new Date, time = this.time, opt = this.opt;
//check if the time interval already exceeds the duration
if(currentTime < time + opt.duration) {
//if not, calculate our animation progress
var delta = (currentTime - time) / opt.duration;
opt.onStep(delta);
} else {
//we already exceeded the duration, stop the effect
//and call the onComplete callback
this.timer = clearInterval(this.timer);
opt.onStep(1);
opt.onComplete();
}
}
};
One very common operation to do with delta is to change the interval [0, 1] of delta to our desired from and to values that we want to compute for our element. A clever thing to do would be to declare this method as a class method for Effect. We'll call it compute:
Effect.compute = function(from, to, delta) {
return from + (to - from) * delta;
};
Now If we wanted for example to animate an element's width style from 0 to 10px we could do:
var elem = document.getElementById("myElementId"),
style = elem.style;
var fx = new Effect({
duration: 500,
onStep: function(delta) {
style.width = Effect.compute(0, 10, delta) + 'px';
}
});
fx.start();
Extending the Effect class
The animation code defined above could be extended in different ways. For example, this class could be slightly modified to accept a DOM element in its constructor and modify style properties of that element when performing an animation. The code could look like this:
var elem = document.getElementById("myElementId");
var fx = new Effect({
element: elem,
duration: 1000
});
fx.start({
'width': [0, 20, 'px'],
'height': [0, 5, 'em']
});
The code would now look more or less like this:
//define the class constructor
function Effect(opt) {
this.opt = {
element: opt.element,
duration: opt.duration || 1000,
fps: opt.fps || 40,
onComplete: opt.onComplete || function(){}
};
}
Effect.prototype = {
//props contains a hash with style properties
start: function(props) {
if(this.timer) return;
var that = this, fps = this.opt.fps;
this.time = +new Date;
this.timer = setInterval(function() { that.step(props); },
Math.round(1000/fps));
},
//triggered at each interval step
step: function(props) {
var currentTime = +new Date, time = this.time, opt = this.opt;
if(currentTime < time + opt.duration) {
var delta = (currentTime - time) / opt.duration;
//set the element style properties
this.setProps(opt.element, props, delta);
} else {
this.timer = clearInterval(this.timer);
this.setProps(opt.element, props, 1);
opt.onComplete();
}
},
//set style properties. Properties must be
//in camelcase format.
setProps: function(elem, props, delta) {
var style = elem.style;
for(var prop in props) {
var values = props[prop];
style[prop] = Effect.compute(values[0], values[1], delta)
+ (values[2] || '');
}
}
};
Other extensions might involve normalizing style keywords, adding effect transitions, adding pause resume methods, and/or using more OO JS idioms when coding these classes.
I hope you got to know a little bit more about animation internals and please if you have any advice on this code, which as I told before is just for demonstration, I'll be pleased to hear you!
Hello there, I'm Nicolas Garcia Belmonte, a Computer Science Engineer from the Buenos Aires Institute of Technology, in Argentina. I live in France now.