Skip to content
Permalink
Browse files

Optimizations to animation queue/promise logic, closes gh-776.

  • Loading branch information...
gnarf authored and dmethvin committed May 23, 2012
1 parent ae20e73 commit 4621a0131b98461e41082aa0aaf73f9c6f4ca9ce
Showing with 224 additions and 180 deletions.
  1. +43 −26 src/effects.js
  2. +45 −80 src/queue.js
  3. +81 −0 test/unit/effects.js
  4. +55 −74 test/unit/queue.js
@@ -4,7 +4,7 @@ var fxNow, timerId, iframe, iframeDoc,
elemdisplay = {},
rfxtypes = /^(?:toggle|show|hide)$/,
rfxnum = /^([\-+]=)?((?:\d*\.)?\d+)([a-z%]*)$/i,
rrun = /\.run$/,
rrun = /queueHooks$/,
animationPrefilters = [ defaultPrefilter ],
tweeners = {
"*": [function( prop, value ) {
@@ -212,12 +212,34 @@ jQuery.Animation = jQuery.extend( Animation, {
});

function defaultPrefilter( elem, props, opts ) {
var index, prop, value, length, dataShow, tween,
var index, prop, value, length, dataShow, tween, hooks, oldfire,
anim = this,
style = elem.style,
orig = {},
handled = [],
hidden = elem.nodeType && isHidden( elem );

// handle queue: false promises
if ( !opts.queue ) {
hooks = jQuery._queueHooks( elem, "fx" );
if ( hooks.unqueued == null ) {
hooks.unqueued = 0;
oldfire = hooks.empty.fire;
hooks.empty.fire = function() {
if ( !hooks.unqueued ) {
oldfire();
}
};
}
hooks.unqueued++;
anim.always(function() {
hooks.unqueued--;
if ( !jQuery.queue( elem, "fx" ).length ) {
hooks.empty.fire();
}
});
}

// height/width overflow pass
if ( elem.nodeType === 1 && ( props.height || props.width ) ) {
// Make sure that nothing sneaks out
@@ -244,7 +266,7 @@ function defaultPrefilter( elem, props, opts ) {

if ( opts.overflow ) {
style.overflow = "hidden";
this.finish(function() {
anim.finish(function() {
style.overflow = opts.overflow[ 0 ];
style.overflowX = opts.overflow[ 1 ];
style.overflowY = opts.overflow[ 2 ];
@@ -270,11 +292,11 @@ function defaultPrefilter( elem, props, opts ) {
if ( hidden ) {
showHide([ elem ], true );
} else {
this.finish(function() {
anim.finish(function() {
showHide([ elem ]);
});
}
this.finish(function() {
anim.finish(function() {
var prop;
jQuery.removeData( elem, "fxshow", true );
for ( prop in orig ) {
@@ -283,7 +305,7 @@ function defaultPrefilter( elem, props, opts ) {
});
for ( index = 0 ; index < length ; index++ ) {
prop = handled[ index ];
tween = this.createTween( prop, hidden ? dataShow[ prop ] : 0 );
tween = anim.createTween( prop, hidden ? dataShow[ prop ] : 0 );
orig[ prop ] = dataShow[ prop ] || jQuery.style( elem, prop );

if ( !( prop in dataShow ) ) {
@@ -482,10 +504,10 @@ jQuery.fn.extend({
this.queue( optall.queue, doAnimation );
},
stop: function( type, clearQueue, gotoEnd ) {
var stopQueue = function( elem, data, index ) {
var hooks = data[ index ];
jQuery.removeData( elem, index, true );
hooks.stop( gotoEnd );
var stopQueue = function( hooks ) {
var stop = hooks.stop;
delete hooks.stop;
stop( gotoEnd );
};

if ( typeof type !== "string" ) {
@@ -498,38 +520,35 @@ jQuery.fn.extend({
}

return this.each(function() {
var index,
hadTimers = false,
var dequeue = true,
index = type != null && type + "queueHooks",
timers = jQuery.timers,
data = jQuery._data( this );

// clear marker counters if we know they won't be
if ( !gotoEnd ) {
jQuery._unmark( true, this );
}

if ( type == null ) {
if ( index ) {
if ( data[ index ] && data[ index ].stop ) {
stopQueue( data[ index ] );
}
} else {
for ( index in data ) {
if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
stopQueue( this, data, index );
stopQueue( data[ index ] );
}
}
} else if ( data[ index = type + ".run" ] && data[ index ].stop ){
stopQueue( this, data, index );
}

for ( index = timers.length; index--; ) {
if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
timers[ index ].anim.stop( gotoEnd );
hadTimers = true;
dequeue = false;
timers.splice( index, 1 );
}
}

// start the next in the queue if the last step wasn't forced
// timers currently will call their complete callbacks, which will dequeue
// but only if they were gotoEnd
if ( !( gotoEnd && hadTimers ) ) {
if ( dequeue || !gotoEnd ) {
jQuery.dequeue( this, type );
}
});
@@ -589,15 +608,13 @@ jQuery.speed = function( speed, easing, fn ) {
// Queueing
opt.old = opt.complete;

opt.complete = function( noUnmark ) {
opt.complete = function() {
if ( jQuery.isFunction( opt.old ) ) {
opt.old.call( this );
}

if ( opt.queue ) {
jQuery.dequeue( this, opt.queue );
} else if ( noUnmark !== false ) {
jQuery._unmark( this );
}
};

@@ -1,68 +1,22 @@
(function( jQuery ) {

function handleQueueMarkDefer( elem, type, src ) {
var deferDataKey = type + "defer",
queueDataKey = type + "queue",
markDataKey = type + "mark",
defer = jQuery._data( elem, deferDataKey );
if ( defer &&
( src === "queue" || !jQuery._data(elem, queueDataKey) ) &&
( src === "mark" || !jQuery._data(elem, markDataKey) ) ) {
// Give room for hard-coded callbacks to fire first
// and eventually mark/queue something else on the element
setTimeout( function() {
if ( !jQuery._data( elem, queueDataKey ) &&
!jQuery._data( elem, markDataKey ) ) {
jQuery.removeData( elem, deferDataKey, true );
defer.fire();
}
}, 0 );
}
}

jQuery.extend({

_mark: function( elem, type ) {
if ( elem ) {
type = ( type || "fx" ) + "mark";
jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 );
}
},

_unmark: function( force, elem, type ) {
if ( force !== true ) {
type = elem;
elem = force;
force = false;
}
if ( elem ) {
type = type || "fx";
var key = type + "mark",
count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 );
if ( count ) {
jQuery._data( elem, key, count );
} else {
jQuery.removeData( elem, key, true );
handleQueueMarkDefer( elem, type, "mark" );
}
}
},

queue: function( elem, type, data ) {
var q;
var queue;

if ( elem ) {
type = ( type || "fx" ) + "queue";
q = jQuery._data( elem, type );
queue = jQuery._data( elem, type );

// Speed up dequeue by getting out quickly if this is just a lookup
if ( data ) {
if ( !q || jQuery.isArray(data) ) {
q = jQuery._data( elem, type, jQuery.makeArray(data) );
if ( !queue || jQuery.isArray(data) ) {
queue = jQuery._data( elem, type, jQuery.makeArray(data) );
} else {
q.push( data );
queue.push( data );
}
}
return q || [];
return queue || [];
}
},

@@ -71,30 +25,42 @@ jQuery.extend({

var queue = jQuery.queue( elem, type ),
fn = queue.shift(),
hooks = {};
hooks = jQuery._queueHooks( elem, type ),
next = function() {
jQuery.dequeue( elem, type );
};

// If the fx queue is dequeued, always remove the progress sentinel
if ( fn === "inprogress" ) {
fn = queue.shift();
}

if ( fn ) {

// Add a progress sentinel to prevent the fx queue from being
// automatically dequeued
if ( type === "fx" ) {
queue.unshift( "inprogress" );
}

jQuery._data( elem, type + ".run", hooks );
fn.call( elem, function() {
jQuery.dequeue( elem, type );
}, hooks );
// clear up the last queue stop function
delete hooks.stop;
fn.call( elem, next, hooks );
}

if ( !queue.length ) {
jQuery.removeData( elem, type + "queue " + type + ".run", true );
handleQueueMarkDefer( elem, type, "queue" );
if ( !queue.length && hooks ) {
hooks.empty.fire();
}
},

// not intended for public consumption - generates a queueHooks object, or returns the current one
_queueHooks: function( elem, type ) {
var key = type + "queueHooks";
return jQuery._data( elem, key ) || jQuery._data( elem, key, {
empty: jQuery.Callbacks("once memory").add(function() {
jQuery.removeData( elem, type + "queue", true );
jQuery.removeData( elem, key, true );
})
});
}
});

@@ -117,6 +83,9 @@ jQuery.fn.extend({
this.each(function() {
var queue = jQuery.queue( this, type, data );

// ensure a hooks for this queue
jQuery._queueHooks( this, type );

if ( type === "fx" && queue[0] !== "inprogress" ) {
jQuery.dequeue( this, type );
}
@@ -146,31 +115,27 @@ jQuery.fn.extend({
// Get a promise resolved when queues of a certain type
// are emptied (fx is the type by default)
promise: function( type, object ) {
var tmp,
count = 1,
defer = jQuery.Deferred(),
elements = this,
i = this.length,
resolve = function() {
if ( !( --count ) ) {
defer.resolveWith( elements, [ elements ] );
}
};

if ( typeof type !== "string" ) {
object = type;
type = undefined;
}
type = type || "fx";
var defer = jQuery.Deferred(),
elements = this,
i = elements.length,
count = 1,
deferDataKey = type + "defer",
queueDataKey = type + "queue",
markDataKey = type + "mark",
tmp;
function resolve() {
if ( !( --count ) ) {
defer.resolveWith( elements, [ elements ] );
}
}

while( i-- ) {
if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) ||
( jQuery.data( elements[ i ], queueDataKey, undefined, true ) ||
jQuery.data( elements[ i ], markDataKey, undefined, true ) ) &&
jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) {
if ( (tmp = jQuery._data( elements[ i ], type + "queueHooks" )) && tmp.empty ) {
count++;
tmp.add( resolve );
tmp.empty.add( resolve );
}
}
resolve();
Oops, something went wrong.

0 comments on commit 4621a01

Please sign in to comment.
You can’t perform that action at this time.