aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCorey Frang <gnarf@gnarf.net>2012-06-22 16:03:39 -0400
committerDave Methvin <dave.methvin@gmail.com>2012-06-22 16:11:12 -0400
commit36369ce50ff276dcf2959add7dc949af83b221c2 (patch)
tree6827c650b64c27a4b67d73d1a2bc16e79cdcc5a6
parent9bb3494ce99889e05a7333e38e4da9579ce6af8e (diff)
downloadjquery-36369ce50ff276dcf2959add7dc949af83b221c2.tar.gz
jquery-36369ce50ff276dcf2959add7dc949af83b221c2.zip
Fix #11797. Use Deferred for better animation callbacks. Closes gh-830.
In particular, an animation stopped with `gotoEnd` will be rejected.
-rw-r--r--src/effects.js77
-rw-r--r--test/unit/effects.js69
2 files changed, 109 insertions, 37 deletions
diff --git a/src/effects.js b/src/effects.js
index dcd7e2cbb..7c43dc178 100644
--- a/src/effects.js
+++ b/src/effects.js
@@ -56,7 +56,7 @@ function createFxNow() {
return ( fxNow = jQuery.now() );
}
-function callTweeners( animation, props ) {
+function createTweens( animation, props ) {
jQuery.each( props, function( prop, value ) {
var collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
index = 0,
@@ -76,16 +76,9 @@ function Animation( elem, properties, options ) {
index = 0,
tweenerIndex = 0,
length = animationPrefilters.length,
- finished = jQuery.Deferred(),
- deferred = jQuery.Deferred().always(function( ended ) {
-
+ deferred = jQuery.Deferred().always( function() {
// don't match elem in the :animated selector
delete tick.elem;
- if ( deferred.state() === "resolved" || ended ) {
-
- // fire callbacks
- finished.resolveWith( this );
- }
}),
tick = function() {
var currentTime = fxNow || createFxNow(),
@@ -101,7 +94,7 @@ function Animation( elem, properties, options ) {
if ( percent < 1 && length ) {
return remaining;
} else {
- deferred.resolveWith( elem, [ currentTime ] );
+ deferred.resolveWith( elem, [ animation ] );
return false;
}
},
@@ -113,7 +106,6 @@ function Animation( elem, properties, options ) {
originalOptions: options,
startTime: fxNow || createFxNow(),
duration: options.duration,
- finish: finished.done,
tweens: [],
createTween: function( prop, end, easing ) {
var tween = jQuery.Tween( elem, animation.opts, prop, end,
@@ -130,7 +122,14 @@ function Animation( elem, properties, options ) {
for ( ; index < length ; index++ ) {
animation.tweens[ index ].run( 1 );
}
- deferred.rejectWith( elem, [ gotoEnd ] );
+
+ // resolve when we played the last frame
+ // otherwise, reject
+ if ( gotoEnd ) {
+ deferred.resolveWith( elem, [ animation, gotoEnd ] );
+ } else {
+ deferred.rejectWith( elem, [ animation, gotoEnd ] );
+ }
return this;
}
}),
@@ -139,14 +138,17 @@ function Animation( elem, properties, options ) {
propFilter( props, animation.opts.specialEasing );
for ( ; index < length ; index++ ) {
- result = animationPrefilters[ index ].call( animation,
- elem, props, animation.opts );
+ result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
if ( result ) {
return result;
}
}
- callTweeners( animation, props );
+ createTweens( animation, props );
+
+ if ( jQuery.isFunction( animation.opts.start ) ) {
+ animation.opts.start.call( elem, animation );
+ }
jQuery.fx.timer(
jQuery.extend( tick, {
@@ -155,7 +157,11 @@ function Animation( elem, properties, options ) {
elem: elem
})
);
- return animation;
+
+ // attach callbacks from options
+ return animation.done( animation.opts.done, animation.opts.complete )
+ .fail( animation.opts.fail )
+ .always( animation.opts.always );
}
function propFilter( props, specialEasing ) {
@@ -246,11 +252,16 @@ function defaultPrefilter( elem, props, opts ) {
};
}
hooks.unqueued++;
+
anim.always(function() {
- hooks.unqueued--;
- if ( !jQuery.queue( elem, "fx" ).length ) {
- hooks.empty.fire();
- }
+ // doing this makes sure that the complete handler will be called
+ // before this completes
+ anim.always(function() {
+ hooks.unqueued--;
+ if ( !jQuery.queue( elem, "fx" ).length ) {
+ hooks.empty.fire();
+ }
+ });
});
}
@@ -281,7 +292,7 @@ function defaultPrefilter( elem, props, opts ) {
if ( opts.overflow ) {
style.overflow = "hidden";
if ( !jQuery.support.shrinkWrapBlocks ) {
- anim.finish(function() {
+ anim.done(function() {
style.overflow = opts.overflow[ 0 ];
style.overflowX = opts.overflow[ 1 ];
style.overflowY = opts.overflow[ 2 ];
@@ -308,11 +319,11 @@ function defaultPrefilter( elem, props, opts ) {
if ( hidden ) {
jQuery( elem ).show();
} else {
- anim.finish(function() {
+ anim.done(function() {
jQuery( elem ).hide();
});
}
- anim.finish(function() {
+ anim.done(function() {
var prop;
jQuery.removeData( elem, "fxshow", true );
for ( prop in orig ) {
@@ -438,19 +449,19 @@ jQuery.fn.extend({
.end().animate({ opacity: to }, speed, easing, callback );
},
animate: function( prop, speed, easing, callback ) {
- var optall = jQuery.speed( speed, easing, callback ),
+ var empty = jQuery.isEmptyObject( prop ),
+ optall = jQuery.speed( speed, easing, callback ),
doAnimation = function() {
- Animation( this, prop, optall ).finish( optall.complete );
- };
+ // Operate on a copy of prop so per-property easing won't be lost
+ var anim = Animation( this, jQuery.extend( {}, prop ), optall );
- if ( jQuery.isEmptyObject( prop ) ) {
- return this.each( optall.complete, [ false ] );
- }
-
- // Do not change referenced properties as per-property easing will be lost
- prop = jQuery.extend( {}, prop );
+ // Empty animations resolve immediately
+ if ( empty ) {
+ anim.stop( true );
+ }
+ };
- return optall.queue === false ?
+ return empty || optall.queue === false ?
this.each( doAnimation ) :
this.queue( optall.queue, doAnimation );
},
diff --git a/test/unit/effects.js b/test/unit/effects.js
index 96fa6cc29..56951d360 100644
--- a/test/unit/effects.js
+++ b/test/unit/effects.js
@@ -1369,7 +1369,7 @@ test("Do not append px to 'fill-opacity' #9548", 1, function() {
});
// Start 1.8 Animation tests
-asyncTest( "jQuery.Animation( object, props, opts )", 1, function() {
+asyncTest( "jQuery.Animation( object, props, opts )", 4, function() {
var testObject = {
foo: 0,
bar: 1,
@@ -1381,11 +1381,16 @@ asyncTest( "jQuery.Animation( object, props, opts )", 1, function() {
width: 200
};
- jQuery.Animation( testObject, testDest, { duration: 1 })
- .done( function() {
- deepEqual( testObject, testDest, "Animated foo and bar" );
+ var animation = jQuery.Animation( testObject, testDest, { duration: 1 });
+ animation.done(function() {
+ for ( var prop in testDest ) {
+ equal( testObject[ prop ], testDest[ prop ], "Animated: " + prop );
+ }
+ animation.done(function() {
+ deepEqual( testObject, testDest, "No unexpected properties" );
start();
});
+ });
});
asyncTest( "Animate Option: step: function( percent, tween )", 1, function() {
@@ -1660,4 +1665,60 @@ asyncTest( "animate does not change start value for non-px animation (#7109)", 1
});
});
+asyncTest("Animation callbacks (#11797)", 8, function() {
+ var targets = jQuery("#foo").children(),
+ done = false;
+
+ targets.eq( 0 ).animate( {}, {
+ duration: 10,
+ done: function() {
+ ok( true, "empty: done" );
+ },
+ fail: function() {
+ ok( false, "empty: fail" );
+ },
+ always: function() {
+ ok( true, "empty: always" );
+ done = true;
+ }
+ });
+
+ ok( done, "animation done" );
+
+ done = false;
+ targets.eq( 1 ).animate({
+ opacity: 0
+ }, {
+ duration: 10,
+ done: function() {
+ ok( false, "stopped: done" );
+ },
+ fail: function() {
+ ok( true, "stopped: fail" );
+ },
+ always: function() {
+ ok( true, "stopped: always" );
+ done = true;
+ }
+ }).stop();
+
+ ok( done, "animation stopped" );
+
+ targets.eq( 2 ).animate({
+ opacity: 0
+ }, {
+ duration: 10,
+ done: function() {
+ ok( true, "async: done" );
+ },
+ fail: function() {
+ ok( false, "async: fail" );
+ },
+ always: function() {
+ ok( true, "async: always" );
+ start();
+ }
+ });
+});
+
} // if ( jQuery.fx )