All timers in JavaScript are based on the native plain old school JavaScript function setInterval() or setTimeout(). Even jQuery uses this internally.
The trick to synchronize timers, is making sure there is only one setInterval() is invoked, so build something ourselves.
An animation can be designed with:
- have 10 steps, each with an interval of 30ms.
- The total animation takes 300ms.
At each step, the current progress/percentage can be calculated:
var percentage = ( currentStep / totalSteps );
Now, each time your function is called by setInterval(), you can set all DOM elements at once to the correct positions. To find out where an element should be at each animation frame, you use the:
var diff = ( to - from );
var stepValue = from + diff * percentage;
The jQuery easing functions can be called directly, and the final statement becomes:
var stepValue = jQuery.easing[ easingMethod ]( percentage, 0, from, diff );
I've turned this into a class:
/**
* Animation timeline, with a callback.
*/
function AnimationTimeline( params, onstep )
{
// Copy values.
// Required:
this.from = params.from || 0; // e.g. 0%
this.to = params.to || 1; // e.g. 100%
this.onstep = onstep || params.onstep; // pass the callback.
// Optional
this.steps = params.steps || 10;
this.duration = params.duration || 300;
this.easing = params.easing || "linear";
// Internal
this._diff = 0;
this._step = 1;
this._timer = 0;
}
jQuery.extend( AnimationTimeline.prototype, {
start: function()
{
if( this.from == this.to )
return;
if( this._timer > 0 )
{
self.console && console.error("DOUBLE START!");
return;
}
var myself = this;
this._diff = ( this.to - this.from );
this._timer = setInterval( function() { myself.doStep() }, this.duration / this.steps );
}
, stop: function()
{
clearInterval( this._timer );
this._timer = -1;
this._queue = [];
}
, doStep: function()
{
// jQuery version of: stepValue = from + diff * percentage;
var percentage = ( this._step / this.steps );
var stepValue = jQuery.easing[ this.easing ]( percentage, 0, this.from, this._diff );
// Next step
var props = { animationId: this._timer + 10
, percentage: percentage
, from: this.from, to: this.to
, step: this._step, steps: this.steps
};
if( ++this._step > this.steps )
{
stepValue = this.to; // avoid rounding errors.
this.stop();
}
// Callback
if( this.onstep( stepValue, props ) === false ) {
this.stop();
}
}
});
And now you can use:
var el1 = $("#element1");
var el2 = $("#element2");
var animation = new AnimationTimeline( {
easing: "swing"
, onstep: function( animprops )
{
// This is called for every animation frame. Set the elements:
el1.css( { left: ..., top: ... } );
el2.css( { left: ..., top: ... } );
}
});
// And start it.
animation.start();
Adding a pause/resume is an exercise for the reader.