mirror of
https://github.com/jquery/jquery.git
synced 2024-10-05 11:34:18 +00:00
Optimizations to animation queue/promise logic, closes gh-776.
This commit is contained in:
parent
ae20e732f0
commit
4621a0131b
69
src/effects.js
vendored
69
src/effects.js
vendored
@ -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,30 +520,27 @@ 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 );
|
||||
}
|
||||
}
|
||||
@ -529,7 +548,7 @@ jQuery.fn.extend({
|
||||
// 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 );
|
||||
}
|
||||
};
|
||||
|
||||
|
125
src/queue.js
125
src/queue.js
@ -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,7 +25,10 @@ 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" ) {
|
||||
@ -79,22 +36,31 @@ jQuery.extend({
|
||||
}
|
||||
|
||||
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 && hooks ) {
|
||||
hooks.empty.fire();
|
||||
}
|
||||
},
|
||||
|
||||
if ( !queue.length ) {
|
||||
jQuery.removeData( elem, type + "queue " + type + ".run", true );
|
||||
handleQueueMarkDefer( elem, type, "queue" );
|
||||
}
|
||||
// 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();
|
||||
|
81
test/unit/effects.js
vendored
81
test/unit/effects.js
vendored
@ -1628,3 +1628,84 @@ asyncTest( "hide, fadeOut and slideUp called on element width height and width =
|
||||
start();
|
||||
});
|
||||
});
|
||||
|
||||
asyncTest( "Handle queue:false promises", 10, function() {
|
||||
var foo = jQuery( "#foo" ).clone().andSelf(),
|
||||
step = 1;
|
||||
|
||||
foo.animate({
|
||||
top: 1
|
||||
}, {
|
||||
duration: 10,
|
||||
queue: false,
|
||||
complete: function() {
|
||||
ok( step++ <= 2, "Step one or two" );
|
||||
}
|
||||
}).animate({
|
||||
bottom: 1
|
||||
}, {
|
||||
duration: 10,
|
||||
complete: function() {
|
||||
ok( step > 2 && step < 5, "Step three or four" );
|
||||
step++;
|
||||
}
|
||||
});
|
||||
|
||||
foo.promise().done( function() {
|
||||
equal( step++, 5, "steps 1-5: queue:false then queue:fx done" );
|
||||
foo.animate({
|
||||
top: 10
|
||||
}, {
|
||||
duration: 10,
|
||||
complete: function() {
|
||||
ok( step > 5 && step < 8, "Step six or seven" );
|
||||
step++;
|
||||
}
|
||||
}).animate({
|
||||
bottom: 10
|
||||
}, {
|
||||
duration: 10,
|
||||
queue: false,
|
||||
complete: function() {
|
||||
ok( step > 7 && step < 10, "Step eight or nine" );
|
||||
step++;
|
||||
}
|
||||
}).promise().done( function() {
|
||||
equal( step++, 10, "steps 6-10: queue:fx then queue:false" );
|
||||
start();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
asyncTest( "multiple unqueued and promise", 4, function() {
|
||||
var foo = jQuery( "#foo" ),
|
||||
step = 1;
|
||||
foo.animate({
|
||||
marginLeft: 300
|
||||
}, {
|
||||
duration: 500,
|
||||
queue: false,
|
||||
complete: function() {
|
||||
strictEqual( step++, 2, "Step 2" );
|
||||
}
|
||||
}).animate({
|
||||
top: 100
|
||||
}, {
|
||||
duration: 1500,
|
||||
queue: false,
|
||||
complete: function() {
|
||||
strictEqual( step++, 3, "Step 3" );
|
||||
}
|
||||
}).animate({}, {
|
||||
duration: 2000,
|
||||
queue: false,
|
||||
complete: function() {
|
||||
// no properties is a non-op and finishes immediately
|
||||
strictEqual( step++, 1, "Step 1" );
|
||||
}
|
||||
}).promise().done( function() {
|
||||
strictEqual( step++, 4, "Step 4" );
|
||||
start();
|
||||
});
|
||||
});
|
@ -1,15 +1,15 @@
|
||||
module("queue", { teardown: moduleTeardown });
|
||||
module( "queue", {
|
||||
teardown: moduleTeardown
|
||||
});
|
||||
|
||||
test("queue() with other types",function() {
|
||||
expect(12);
|
||||
test( "queue() with other types", 12, function() {
|
||||
var counter = 0;
|
||||
|
||||
stop();
|
||||
|
||||
var $div = jQuery({}),
|
||||
defer;
|
||||
|
||||
$div.promise("foo").done(function() {
|
||||
$div.promise( "foo" ).done(function() {
|
||||
equal( counter, 0, "Deferred for collection with no queue is automatically resolved" );
|
||||
});
|
||||
|
||||
@ -30,7 +30,7 @@ test("queue() with other types",function() {
|
||||
});
|
||||
|
||||
defer = $div.promise("foo").done(function() {
|
||||
equal( counter, 4, "Testing previous call to dequeue in deferred" );
|
||||
equal( counter, 4, "Testing previous call to dequeue in deferred" );
|
||||
start();
|
||||
});
|
||||
|
||||
@ -216,85 +216,46 @@ test("clearQueue() clears the fx queue", function() {
|
||||
div.removeData();
|
||||
});
|
||||
|
||||
test("_mark() and _unmark()", function() {
|
||||
expect(1);
|
||||
asyncTest( "fn.promise() - called when fx queue is empty", 3, function() {
|
||||
var foo = jQuery( "#foo" ).clone().andSelf(),
|
||||
promised = false;
|
||||
|
||||
var div = {},
|
||||
$div = jQuery( div );
|
||||
|
||||
stop();
|
||||
|
||||
jQuery._mark( div, "foo" );
|
||||
jQuery._mark( div, "foo" );
|
||||
jQuery._unmark( div, "foo" );
|
||||
jQuery._unmark( div, "foo" );
|
||||
|
||||
$div.promise( "foo" ).done(function() {
|
||||
ok( true, "No more marks" );
|
||||
foo.queue( function( next ) {
|
||||
// called twice!
|
||||
ok( !promised, "Promised hasn't been called" );
|
||||
setTimeout( next, 10 );
|
||||
});
|
||||
foo.promise().done( function() {
|
||||
ok( promised = true, "Promised" );
|
||||
start();
|
||||
});
|
||||
});
|
||||
|
||||
test("_mark() and _unmark() default to 'fx'", function() {
|
||||
expect(1);
|
||||
|
||||
var div = {},
|
||||
$div = jQuery( div );
|
||||
|
||||
stop();
|
||||
|
||||
jQuery._mark( div );
|
||||
jQuery._mark( div );
|
||||
jQuery._unmark( div, "fx" );
|
||||
jQuery._unmark( div );
|
||||
|
||||
$div.promise().done(function() {
|
||||
ok( true, "No more marks" );
|
||||
start();
|
||||
asyncTest( "fn.promise( \"queue\" ) - called whenever last queue function is dequeued", 5, function() {
|
||||
var foo = jQuery( "#foo" ),
|
||||
test;
|
||||
foo.promise( "queue" ).done( function() {
|
||||
strictEqual( test, undefined, "called immediately when queue was already empty" );
|
||||
});
|
||||
});
|
||||
|
||||
test("promise()", function() {
|
||||
expect(1);
|
||||
|
||||
stop();
|
||||
|
||||
var objects = [];
|
||||
|
||||
jQuery.each( [{}, {}], function( i, div ) {
|
||||
var $div = jQuery( div );
|
||||
$div.queue(function( next ) {
|
||||
setTimeout( function() {
|
||||
if ( i ) {
|
||||
next();
|
||||
setTimeout( function() {
|
||||
jQuery._unmark( div );
|
||||
}, 20 );
|
||||
} else {
|
||||
jQuery._unmark( div );
|
||||
setTimeout( function() {
|
||||
next();
|
||||
}, 20 );
|
||||
}
|
||||
}, 50 );
|
||||
}).queue(function( next ) {
|
||||
test = 1;
|
||||
foo.queue( "queue", function( next ) {
|
||||
strictEqual( test++, 1, "step one" );
|
||||
setTimeout( next, 0 );
|
||||
}).queue( "queue", function( next ) {
|
||||
strictEqual( test++, 2, "step two" );
|
||||
setTimeout( function() {
|
||||
strictEqual( test++, 4, "step four" );
|
||||
next();
|
||||
});
|
||||
jQuery._mark( div );
|
||||
objects.push( $div );
|
||||
start();
|
||||
}, 10 );
|
||||
}).promise( "queue" ).done( function() {
|
||||
strictEqual( test++, 3, "step three" );
|
||||
});
|
||||
|
||||
jQuery.when.apply( jQuery, objects ).done(function() {
|
||||
ok( true, "Deferred resolved" );
|
||||
start();
|
||||
});
|
||||
|
||||
jQuery.each( objects, function() {
|
||||
this.dequeue();
|
||||
});
|
||||
foo.dequeue( "queue" );
|
||||
});
|
||||
|
||||
test(".promise(obj)", function() {
|
||||
test( ".promise(obj)", function() {
|
||||
expect(2);
|
||||
|
||||
var obj = {};
|
||||
@ -303,3 +264,23 @@ test(".promise(obj)", function() {
|
||||
ok( jQuery.isFunction( promise.promise ), ".promise(type, obj) returns a promise" );
|
||||
strictEqual( promise, obj, ".promise(type, obj) returns obj" );
|
||||
});
|
||||
|
||||
asyncTest( "queue stop hooks", 2, function() {
|
||||
var foo = jQuery( "#foo" );
|
||||
|
||||
foo.queue( function( next, hooks ) {
|
||||
hooks.stop = function( gotoEnd ) {
|
||||
equal( !!gotoEnd, false, "Stopped without gotoEnd" );
|
||||
};
|
||||
});
|
||||
foo.stop();
|
||||
|
||||
foo.queue( function( next, hooks ) {
|
||||
hooks.stop = function( gotoEnd ) {
|
||||
equal( gotoEnd, true, "Stopped with gotoEnd" );
|
||||
start();
|
||||
};
|
||||
});
|
||||
|
||||
foo.stop( false, true );
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user