mirror of
https://github.com/jquery/jquery.git
synced 2025-01-10 18:24:24 +00:00
Fix #11797. Use Deferred for better animation callbacks. Closes gh-830.
In particular, an animation stopped with `gotoEnd` will be rejected.
This commit is contained in:
parent
9bb3494ce9
commit
36369ce50f
77
src/effects.js
vendored
77
src/effects.js
vendored
@ -56,7 +56,7 @@ function createFxNow() {
|
|||||||
return ( fxNow = jQuery.now() );
|
return ( fxNow = jQuery.now() );
|
||||||
}
|
}
|
||||||
|
|
||||||
function callTweeners( animation, props ) {
|
function createTweens( animation, props ) {
|
||||||
jQuery.each( props, function( prop, value ) {
|
jQuery.each( props, function( prop, value ) {
|
||||||
var collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
|
var collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
|
||||||
index = 0,
|
index = 0,
|
||||||
@ -76,16 +76,9 @@ function Animation( elem, properties, options ) {
|
|||||||
index = 0,
|
index = 0,
|
||||||
tweenerIndex = 0,
|
tweenerIndex = 0,
|
||||||
length = animationPrefilters.length,
|
length = animationPrefilters.length,
|
||||||
finished = jQuery.Deferred(),
|
deferred = jQuery.Deferred().always( function() {
|
||||||
deferred = jQuery.Deferred().always(function( ended ) {
|
|
||||||
|
|
||||||
// don't match elem in the :animated selector
|
// don't match elem in the :animated selector
|
||||||
delete tick.elem;
|
delete tick.elem;
|
||||||
if ( deferred.state() === "resolved" || ended ) {
|
|
||||||
|
|
||||||
// fire callbacks
|
|
||||||
finished.resolveWith( this );
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
tick = function() {
|
tick = function() {
|
||||||
var currentTime = fxNow || createFxNow(),
|
var currentTime = fxNow || createFxNow(),
|
||||||
@ -101,7 +94,7 @@ function Animation( elem, properties, options ) {
|
|||||||
if ( percent < 1 && length ) {
|
if ( percent < 1 && length ) {
|
||||||
return remaining;
|
return remaining;
|
||||||
} else {
|
} else {
|
||||||
deferred.resolveWith( elem, [ currentTime ] );
|
deferred.resolveWith( elem, [ animation ] );
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -113,7 +106,6 @@ function Animation( elem, properties, options ) {
|
|||||||
originalOptions: options,
|
originalOptions: options,
|
||||||
startTime: fxNow || createFxNow(),
|
startTime: fxNow || createFxNow(),
|
||||||
duration: options.duration,
|
duration: options.duration,
|
||||||
finish: finished.done,
|
|
||||||
tweens: [],
|
tweens: [],
|
||||||
createTween: function( prop, end, easing ) {
|
createTween: function( prop, end, easing ) {
|
||||||
var tween = jQuery.Tween( elem, animation.opts, prop, end,
|
var tween = jQuery.Tween( elem, animation.opts, prop, end,
|
||||||
@ -130,7 +122,14 @@ function Animation( elem, properties, options ) {
|
|||||||
for ( ; index < length ; index++ ) {
|
for ( ; index < length ; index++ ) {
|
||||||
animation.tweens[ index ].run( 1 );
|
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;
|
return this;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@ -139,14 +138,17 @@ function Animation( elem, properties, options ) {
|
|||||||
propFilter( props, animation.opts.specialEasing );
|
propFilter( props, animation.opts.specialEasing );
|
||||||
|
|
||||||
for ( ; index < length ; index++ ) {
|
for ( ; index < length ; index++ ) {
|
||||||
result = animationPrefilters[ index ].call( animation,
|
result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
|
||||||
elem, props, animation.opts );
|
|
||||||
if ( result ) {
|
if ( result ) {
|
||||||
return 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.fx.timer(
|
||||||
jQuery.extend( tick, {
|
jQuery.extend( tick, {
|
||||||
@ -155,7 +157,11 @@ function Animation( elem, properties, options ) {
|
|||||||
elem: elem
|
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 ) {
|
function propFilter( props, specialEasing ) {
|
||||||
@ -246,11 +252,16 @@ function defaultPrefilter( elem, props, opts ) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
hooks.unqueued++;
|
hooks.unqueued++;
|
||||||
|
|
||||||
anim.always(function() {
|
anim.always(function() {
|
||||||
hooks.unqueued--;
|
// doing this makes sure that the complete handler will be called
|
||||||
if ( !jQuery.queue( elem, "fx" ).length ) {
|
// before this completes
|
||||||
hooks.empty.fire();
|
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 ) {
|
if ( opts.overflow ) {
|
||||||
style.overflow = "hidden";
|
style.overflow = "hidden";
|
||||||
if ( !jQuery.support.shrinkWrapBlocks ) {
|
if ( !jQuery.support.shrinkWrapBlocks ) {
|
||||||
anim.finish(function() {
|
anim.done(function() {
|
||||||
style.overflow = opts.overflow[ 0 ];
|
style.overflow = opts.overflow[ 0 ];
|
||||||
style.overflowX = opts.overflow[ 1 ];
|
style.overflowX = opts.overflow[ 1 ];
|
||||||
style.overflowY = opts.overflow[ 2 ];
|
style.overflowY = opts.overflow[ 2 ];
|
||||||
@ -308,11 +319,11 @@ function defaultPrefilter( elem, props, opts ) {
|
|||||||
if ( hidden ) {
|
if ( hidden ) {
|
||||||
jQuery( elem ).show();
|
jQuery( elem ).show();
|
||||||
} else {
|
} else {
|
||||||
anim.finish(function() {
|
anim.done(function() {
|
||||||
jQuery( elem ).hide();
|
jQuery( elem ).hide();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
anim.finish(function() {
|
anim.done(function() {
|
||||||
var prop;
|
var prop;
|
||||||
jQuery.removeData( elem, "fxshow", true );
|
jQuery.removeData( elem, "fxshow", true );
|
||||||
for ( prop in orig ) {
|
for ( prop in orig ) {
|
||||||
@ -438,19 +449,19 @@ jQuery.fn.extend({
|
|||||||
.end().animate({ opacity: to }, speed, easing, callback );
|
.end().animate({ opacity: to }, speed, easing, callback );
|
||||||
},
|
},
|
||||||
animate: function( prop, 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() {
|
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 );
|
||||||
|
|
||||||
|
// Empty animations resolve immediately
|
||||||
|
if ( empty ) {
|
||||||
|
anim.stop( true );
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if ( jQuery.isEmptyObject( prop ) ) {
|
return empty || optall.queue === false ?
|
||||||
return this.each( optall.complete, [ false ] );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do not change referenced properties as per-property easing will be lost
|
|
||||||
prop = jQuery.extend( {}, prop );
|
|
||||||
|
|
||||||
return optall.queue === false ?
|
|
||||||
this.each( doAnimation ) :
|
this.each( doAnimation ) :
|
||||||
this.queue( optall.queue, doAnimation );
|
this.queue( optall.queue, doAnimation );
|
||||||
},
|
},
|
||||||
|
69
test/unit/effects.js
vendored
69
test/unit/effects.js
vendored
@ -1369,7 +1369,7 @@ test("Do not append px to 'fill-opacity' #9548", 1, function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Start 1.8 Animation tests
|
// Start 1.8 Animation tests
|
||||||
asyncTest( "jQuery.Animation( object, props, opts )", 1, function() {
|
asyncTest( "jQuery.Animation( object, props, opts )", 4, function() {
|
||||||
var testObject = {
|
var testObject = {
|
||||||
foo: 0,
|
foo: 0,
|
||||||
bar: 1,
|
bar: 1,
|
||||||
@ -1381,11 +1381,16 @@ asyncTest( "jQuery.Animation( object, props, opts )", 1, function() {
|
|||||||
width: 200
|
width: 200
|
||||||
};
|
};
|
||||||
|
|
||||||
jQuery.Animation( testObject, testDest, { duration: 1 })
|
var animation = jQuery.Animation( testObject, testDest, { duration: 1 });
|
||||||
.done( function() {
|
animation.done(function() {
|
||||||
deepEqual( testObject, testDest, "Animated foo and bar" );
|
for ( var prop in testDest ) {
|
||||||
|
equal( testObject[ prop ], testDest[ prop ], "Animated: " + prop );
|
||||||
|
}
|
||||||
|
animation.done(function() {
|
||||||
|
deepEqual( testObject, testDest, "No unexpected properties" );
|
||||||
start();
|
start();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
asyncTest( "Animate Option: step: function( percent, tween )", 1, function() {
|
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 )
|
} // if ( jQuery.fx )
|
||||||
|
Loading…
Reference in New Issue
Block a user