views:

543

answers:

3

I'd like to implement an animation in my site which:

  • needs to update multiple DOM elements.
  • each DOM element has it's own animation path (depends on their position)
  • and still have an easing effect.

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

} );
+2  A: 

Hi, did you check jquery queue?

You can queue your animations and set a callback on each of them, I think if you play little bit with it you can achieve what you want.

Hope it helps, Sinan.

Sinan Y.
A: 

look at this article: http://james.padolsey.com/javascript/fun-with-jquerys-animate/

very clear, very easy!

+3  A: 

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.

vdboor