From c27d8e298832b25836dc6ff698cd8400ef207e13 Mon Sep 17 00:00:00 2001 From: Corey Frang Date: Tue, 8 Jan 2013 01:16:50 +0000 Subject: [PATCH] Fix #13103. Add .finish() method. Cherry picked from b6abb31df4d5be80dc13844d9988fb0c990ae5ae. --- src/effects.js | 59 +++++++++++++++++++++++++++++++++++++++++--- src/queue.js | 1 + test/unit/effects.js | 49 ++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 4 deletions(-) diff --git a/src/effects.js b/src/effects.js index 3d9ae7f4b..7ab3a0869 100644 --- a/src/effects.js +++ b/src/effects.js @@ -72,6 +72,7 @@ function createTweens( animation, props ) { function Animation( elem, properties, options ) { var result, + stopped, index = 0, length = animationPrefilters.length, deferred = jQuery.Deferred().always( function() { @@ -79,6 +80,9 @@ function Animation( elem, properties, options ) { delete tick.elem; }), tick = function() { + if ( stopped ) { + return false; + } var currentTime = fxNow || createFxNow(), remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), // archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497) @@ -120,7 +124,10 @@ function Animation( elem, properties, options ) { // if we are going to the end, we want to run all the tweens // otherwise we skip this part length = gotoEnd ? animation.tweens.length : 0; - + if ( stopped ) { + return this; + } + stopped = true; for ( ; index < length ; index++ ) { animation.tweens[ index ].run( 1 ); } @@ -477,12 +484,15 @@ jQuery.fn.extend({ doAnimation = function() { // Operate on a copy of prop so per-property easing won't be lost var anim = Animation( this, jQuery.extend( {}, prop ), optall ); - - // Empty animations resolve immediately - if ( empty ) { + doAnimation.finish = function() { + anim.stop( true ); + }; + // Empty animations, or finishing resolves immediately + if ( empty || jQuery._data( this, "finish" ) ) { anim.stop( true ); } }; + doAnimation.finish = doAnimation; return empty || optall.queue === false ? this.each( doAnimation ) : @@ -537,6 +547,47 @@ jQuery.fn.extend({ jQuery.dequeue( this, type ); } }); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each(function() { + var index, + data = jQuery._data( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // enable finishing flag on private data + data.finish = true; + + // empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.cur && hooks.cur.finish ) { + hooks.cur.finish.call( this ); + } + + // look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // turn off finishing flag + delete data.finish; + }); } }); diff --git a/src/queue.js b/src/queue.js index c5a0cbd7d..d4c3f0040 100644 --- a/src/queue.js +++ b/src/queue.js @@ -35,6 +35,7 @@ jQuery.extend({ startLength--; } + hooks.cur = fn; if ( fn ) { // Add a progress sentinel to prevent the fx queue from being diff --git a/test/unit/effects.js b/test/unit/effects.js index b615dfd6d..965e9969e 100644 --- a/test/unit/effects.js +++ b/test/unit/effects.js @@ -1904,4 +1904,53 @@ test( "jQuery.fx.start & jQuery.fx.stop hook points", function() { jQuery.fx.stop = oldStop; }); +test( ".finish() completes all queued animations", function() { + var animations = { + top: 100, + left: 100, + height: 100, + width: 100 + }, + div = jQuery("
"); + + expect( 11 ); + + jQuery.each( animations, function( prop, value ) { + var anim = {}; + anim[ prop ] = value; + // the delay shouldn't matter at all! + div.css( prop, 1 ).animate( anim, function() { + ok( true, "Called animation callback" ); + }).delay( 100 ); + }); + equal( div.queue().length, 8, "8 animations in the queue" ); + div.finish(); + jQuery.each( animations, function( prop, value ) { + equal( parseFloat( div.css( prop ) ), value, prop + " finished at correct value" ); + }); + equal( div.queue().length, 0, "empty queue when done" ); + equal( div.is(":animated"), false, ":animated doesn't match" ); + + // cleanup + div.remove(); + // leaves a "shadow timer" which does nothing around, need to force a tick + jQuery.fx.tick(); +}); + +test( ".finish() calls finish of custom queue functions", function() { + function queueTester( next ) { + + } + var div = jQuery( "
" ); + + expect( 3 ); + queueTester.finish = function() { + ok( true, "Finish called on custom queue function" ); + }; + + div.queue( queueTester ).queue( queueTester ).queue( queueTester ).finish(); + + div.remove(); +}); + } // if ( jQuery.fx ) -- 2.39.5