I'd like to implement an animation in my site which:
If I call jQuery's animate() function for each element (with queue: false), it will make each element move slightly out of sync with the rest. Makes sense, since there are multiple timers running.
Can I have just one timer event, with a callback for each animation step? Something like:
jQuery.fx.timeline( from, to, easing, function( step, total ) {
var percentage = step / total;
// ...update DOM elements based on the percentage of the animation
} );
look at this article: http://james.padolsey.com/javascript/fun-with-jquerys-animate/
very clear, very easy!
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:
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( stepValue, 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With