mirror of
https://github.com/jquery/jquery.git
synced 2024-11-23 02:54:22 +00:00
Effects: stabilize rAF logic & align timeout logic with it
rAF logic was introduced almost three years ago relative to this commit, as a primary method for scheduling animation (see gh-1578 pull). With it there was two substantial changes - one was explicitly mentioned and the other was not. First, if browser window was hidden aka `document.hidden === true` it would immediately execute all scheduled animation without waiting for time pass i.e. tick time become `0` instead of 13 ms of a default value. Which created possibility for circular executions in case if `complete` method executed the same animation (see gh-3434 issue). And the second one - since then there was two ways of scheduling animation: with `setInterval` and `requestAnimationFrame`, but there was a difference in their execution. In case of `setInterval` it waited default `jQuery.fx.interval` value before actually starting the new tick, not counting the first step which wasn't set to be executed through tick method (aka `jQuery.fx.tick`). Whereas `requestAnimationFrame` first scheduled the call and executed the `step` method right after that, counting the first call of `jQuery.fx.timer`, `tick` was happening twice in one frame. But since tests explicitly disabled rAF method i.e. `requestAnimationFrame = null` and checking only `setInterval` logic, since it's impossible to do it otherwise - we missed that change. Faulty logic also was presented with `cancelAnimationFrame`, which couldn't clear any timers since `raf` scheduler didn't define new `timerId` value. Because that change was so subtle, apparently no user noticed it proving that both `cancelAnimationFrame` and `clearInterval` code paths are redundant. Since `cancelAnimationFrame` didn't work properly and rAF is and was a primary used code path, plus the same approach is used in other popular animation libs. Therefore those code paths were removed. These changes also replace two different functions which schedule the animation with one, which checks what type of logic should be used and executes it appropriatley, but for secondary path it now uses `setTimeout` making it more consistent with rAF path. Since ticks are happening globally we also don't require to listen `visibilitychange` event. It also changes the way how first call is scheduled so execution of animation will not happen twice in one frame. No new tests were not introduced, since now `setTimeout` logic should be equivalent to the rAF one, but one test was changed since now we actually execute animation at the first tick. Fixes gh-3434 Closes gh-3559
This commit is contained in:
parent
ac9e301664
commit
6d43dc4233
47
src/effects.js
vendored
47
src/effects.js
vendored
@ -23,13 +23,18 @@ define( [
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var
|
var
|
||||||
fxNow, timerId,
|
fxNow, inProgress,
|
||||||
rfxtypes = /^(?:toggle|show|hide)$/,
|
rfxtypes = /^(?:toggle|show|hide)$/,
|
||||||
rrun = /queueHooks$/;
|
rrun = /queueHooks$/;
|
||||||
|
|
||||||
function raf() {
|
function schedule() {
|
||||||
if ( timerId ) {
|
if ( inProgress ) {
|
||||||
window.requestAnimationFrame( raf );
|
if ( document.hidden === false && window.requestAnimationFrame ) {
|
||||||
|
window.requestAnimationFrame( schedule );
|
||||||
|
} else {
|
||||||
|
window.setTimeout( schedule, jQuery.fx.interval );
|
||||||
|
}
|
||||||
|
|
||||||
jQuery.fx.tick();
|
jQuery.fx.tick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -458,8 +463,8 @@ jQuery.speed = function( speed, easing, fn ) {
|
|||||||
easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
|
easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
|
||||||
};
|
};
|
||||||
|
|
||||||
// Go to the end state if fx are off or if document is hidden
|
// Go to the end state if fx are off
|
||||||
if ( jQuery.fx.off || document.hidden ) {
|
if ( jQuery.fx.off ) {
|
||||||
opt.duration = 0;
|
opt.duration = 0;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -664,36 +669,22 @@ jQuery.fx.tick = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
jQuery.fx.timer = function( timer ) {
|
jQuery.fx.timer = function( timer ) {
|
||||||
var i = jQuery.timers.push( timer ) - 1,
|
jQuery.timers.push( timer );
|
||||||
timers = jQuery.timers;
|
jQuery.fx.start();
|
||||||
|
|
||||||
if ( timer() ) {
|
|
||||||
jQuery.fx.start();
|
|
||||||
|
|
||||||
// If the timer finished immediately, safely remove it (allowing for external removal)
|
|
||||||
// Use a superfluous post-decrement for better compressibility w.r.t. jQuery.fx.tick above
|
|
||||||
} else if ( timers[ i ] === timer ) {
|
|
||||||
timers.splice( i--, 1 );
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
jQuery.fx.interval = 13;
|
jQuery.fx.interval = 13;
|
||||||
jQuery.fx.start = function() {
|
jQuery.fx.start = function() {
|
||||||
if ( !timerId ) {
|
if ( inProgress ) {
|
||||||
timerId = window.requestAnimationFrame ?
|
return;
|
||||||
window.requestAnimationFrame( raf ) :
|
|
||||||
window.setInterval( jQuery.fx.tick, jQuery.fx.interval );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inProgress = true;
|
||||||
|
schedule();
|
||||||
};
|
};
|
||||||
|
|
||||||
jQuery.fx.stop = function() {
|
jQuery.fx.stop = function() {
|
||||||
if ( window.cancelAnimationFrame ) {
|
inProgress = null;
|
||||||
window.cancelAnimationFrame( timerId );
|
|
||||||
} else {
|
|
||||||
window.clearInterval( timerId );
|
|
||||||
}
|
|
||||||
|
|
||||||
timerId = null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
jQuery.fx.speeds = {
|
jQuery.fx.speeds = {
|
||||||
|
11
test/unit/effects.js
vendored
11
test/unit/effects.js
vendored
@ -1847,12 +1847,12 @@ QUnit.test( "non-px animation handles non-numeric start (#11971)", function( ass
|
|||||||
} );
|
} );
|
||||||
|
|
||||||
QUnit.test( "Animation callbacks (#11797)", function( assert ) {
|
QUnit.test( "Animation callbacks (#11797)", function( assert ) {
|
||||||
assert.expect( 16 );
|
assert.expect( 15 );
|
||||||
|
|
||||||
var prog = 0,
|
var prog = 0,
|
||||||
targets = jQuery( "#foo" ).children(),
|
targets = jQuery( "#foo" ).children(),
|
||||||
done = false,
|
done = false,
|
||||||
expectedProgress = 0;
|
expectedProgress = 1;
|
||||||
|
|
||||||
targets.eq( 0 ).animate( {}, {
|
targets.eq( 0 ).animate( {}, {
|
||||||
duration: 1,
|
duration: 1,
|
||||||
@ -1910,14 +1910,7 @@ QUnit.test( "Animation callbacks (#11797)", function( assert ) {
|
|||||||
assert.ok( true, "async: start" );
|
assert.ok( true, "async: start" );
|
||||||
},
|
},
|
||||||
progress: function( anim, percent ) {
|
progress: function( anim, percent ) {
|
||||||
|
|
||||||
// occasionally the progress handler is called twice in first frame.... *shrug*
|
|
||||||
if ( percent === 0 && expectedProgress === 1 ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
assert.equal( percent, expectedProgress, "async: progress " + expectedProgress );
|
assert.equal( percent, expectedProgress, "async: progress " + expectedProgress );
|
||||||
|
|
||||||
// once at 0, once at 1
|
|
||||||
expectedProgress++;
|
expectedProgress++;
|
||||||
},
|
},
|
||||||
done: function() {
|
done: function() {
|
||||||
|
Loading…
Reference in New Issue
Block a user