Skip to content

Commit 6fd73d0

Browse files
committed
Effects: Use requestAnimationFrame timestamp if available
In some environments that support the requestAnimationFrame timestamp callback parameter using it results in smoother animations. Note: the rAF timestamp is using the same API as performance.now() under the hood so they're compatible with each other. However, some browsers support rAF (with a timestamp parameter) but not performance.now() so using them both would introduce an error. This commit stops using rAF in browsers that don't support performance.now(). From all the browsers jQuery supports this only affects iOS <9 (currently less than 5% of all iOS users) which will now not use rAF. Fixes gh-3143 Closes gh-3151
1 parent f810080 commit 6fd73d0

File tree

6 files changed

+93
-23
lines changed

6 files changed

+93
-23
lines changed

Gruntfile.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ module.exports = function( grunt ) {
108108
"deprecated",
109109
"dimensions",
110110
"effects",
111+
"effects-nostubs",
111112
"event",
112113
"manipulation",
113114
"offset",

src/effects.js

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,31 @@ var
2727
rfxtypes = /^(?:toggle|show|hide)$/,
2828
rrun = /queueHooks$/;
2929

30-
function schedule() {
30+
function schedule( timestamp ) {
3131
if ( inProgress ) {
32-
if ( document.hidden === false && window.requestAnimationFrame ) {
32+
if ( document.hidden === false ) {
3333
window.requestAnimationFrame( schedule );
3434
} else {
3535
window.setTimeout( schedule, jQuery.fx.interval );
3636
}
3737

38-
jQuery.fx.tick();
38+
jQuery.fx.tick( timestamp );
3939
}
4040
}
4141

42+
// We need to be using Date.now() or performance.now() consistently as they return different
43+
// values: performance.now() counter starts on page load.
44+
// Support: IE <10, Safari <8.0, iOS <9, Android <4.4, Node with jsdom 9.4
45+
function getTimestamp() {
46+
return window.performance.now();
47+
}
48+
4249
// Animations created synchronously will run synchronously
4350
function createFxNow() {
4451
window.setTimeout( function() {
4552
fxNow = undefined;
4653
} );
47-
return ( fxNow = Date.now() );
54+
return ( fxNow = getTimestamp() );
4855
}
4956

5057
// Generate parameters to create a standard animation
@@ -644,12 +651,12 @@ jQuery.each( {
644651
} );
645652

646653
jQuery.timers = [];
647-
jQuery.fx.tick = function() {
654+
jQuery.fx.tick = function( timestamp ) {
648655
var timer,
649656
i = 0,
650657
timers = jQuery.timers;
651658

652-
fxNow = Date.now();
659+
fxNow = timestamp || getTimestamp();
653660

654661
for ( ; i < timers.length; i++ ) {
655662
timer = timers[ i ];
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr" id="html">
3+
<head>
4+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5+
<title>Effects: matching timestamps</title>
6+
</head>
7+
<body>
8+
<script src="../../jquery.js"></script>
9+
<script src="../iframeTest.js"></script>
10+
<div id="test-div" style="height: 1000px;"></div>
11+
<script>
12+
//<![CDATA[
13+
setTimeout( function () {
14+
15+
// Handle a timeout.
16+
startIframeTest( false, "The test timed out" );
17+
}, 5000 );
18+
19+
var maxNow = 0;
20+
21+
jQuery( "#test-div" ).animate( {
22+
height: '2000px',
23+
}, {
24+
duration: 300,
25+
step: function( now ) {
26+
if ( maxNow - now > 100 ) {
27+
startIframeTest( false, "Animation is stepping back from " + maxNow + " to " + now );
28+
}
29+
maxNow = Math.max( now, maxNow );
30+
},
31+
complete: function() {
32+
startIframeTest( true );
33+
}
34+
} );
35+
//]]>
36+
</script>
37+
</body>
38+
</html>

test/unit/animation.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@ if ( !jQuery.fx ) {
55
return;
66
}
77

8-
var oldRaf = window.requestAnimationFrame,
9-
defaultPrefilter = jQuery.Animation.prefilters[ 0 ],
10-
defaultTweener = jQuery.Animation.tweeners[ "*" ][ 0 ],
11-
startTime = 505877050;
8+
var defaultPrefilter = jQuery.Animation.prefilters[ 0 ];
9+
var defaultTweener = jQuery.Animation.tweeners[ "*" ][ 0 ];
10+
var startTime = 505877050;
1211

1312
// This module tests jQuery.Animation and the corresponding 1.8+ effects APIs
1413
QUnit.module( "animation", {
@@ -26,7 +25,6 @@ QUnit.module( "animation", {
2625
this.sandbox.restore();
2726
jQuery.fx.stop();
2827
jQuery.fx.interval = this._oldInterval;
29-
window.requestAnimationFrame = oldRaf;
3028
return moduleTeardown.apply( this, arguments );
3129
}
3230
} );

test/unit/effects.js

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,42 @@ if ( !jQuery.fx ) {
55
return;
66
}
77

8-
var oldRaf = window.requestAnimationFrame,
9-
hideOptions = {
10-
inline: function() { jQuery.style( this, "display", "none" ); },
11-
cascade: function() { this.className = "hidden"; }
12-
};
8+
var hideOptions = {
9+
inline: function() { jQuery.style( this, "display", "none" ); },
10+
cascade: function() { this.className = "hidden"; }
11+
};
1312

1413
QUnit.module( "effects", {
1514
beforeEach: function() {
1615
this.sandbox = sinon.createSandbox();
1716
this.clock = this.sandbox.useFakeTimers( 505877050 );
17+
1818
this._oldInterval = jQuery.fx.interval;
19-
window.requestAnimationFrame = null;
2019
jQuery.fx.step = {};
2120
jQuery.fx.interval = 10;
21+
22+
// Simulate rAF using the mocked setTimeout as otherwise we couldn't test
23+
// the rAF + performance.now code path.
24+
this.sandbox
25+
.stub( window, "requestAnimationFrame" )
26+
.callsFake( function( callback ) {
27+
return window.setTimeout( callback, jQuery.fx.interval,
28+
Date.now() - 99989.6394 );
29+
} );
30+
31+
// Fake performance.now() returning lower values than Date.now()
32+
// and that its values are fractional.
33+
this.sandbox.stub( window.performance, "now" ).callsFake( function() {
34+
return Date.now() - 99999.6394;
35+
} );
2236
},
2337
afterEach: function() {
2438
this.sandbox.restore();
2539
jQuery.fx.stop();
2640
jQuery.fx.interval = this._oldInterval;
27-
window.requestAnimationFrame = oldRaf;
2841
return moduleTeardown.apply( this, arguments );
2942
}
30-
} );
43+
}, function() {
3144

3245
QUnit[ QUnit.jQuerySelectors ? "test" : "skip" ]( "sanity check", function( assert ) {
3346
assert.expect( 1 );
@@ -2623,4 +2636,21 @@ QUnit.test( "jQuery.speed() - durations", function( assert ) {
26232636
jQuery.fx.off = false;
26242637
} );
26252638

2639+
} );
2640+
2641+
QUnit.module( "effects-nostubs", function() {
2642+
2643+
testIframe(
2644+
"matching timestamp",
2645+
"effects/matchingTimestamps.html",
2646+
function( assert, framejQuery, frameWin, frameDoc, success, failureReason ) {
2647+
assert.expect( 1 );
2648+
2649+
assert.ok( success, failureReason );
2650+
}
2651+
);
2652+
2653+
} );
2654+
26262655
} )();
2656+

test/unit/tween.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,18 @@ if ( !jQuery.fx ) {
55
return;
66
}
77

8-
var oldRaf = window.requestAnimationFrame;
9-
108
QUnit.module( "tween", {
119
beforeEach: function() {
1210
this.sandbox = sinon.createSandbox();
1311
this.clock = this.sandbox.useFakeTimers( 505877050 );
1412
this._oldInterval = jQuery.fx.interval;
15-
window.requestAnimationFrame = null;
1613
jQuery.fx.step = {};
1714
jQuery.fx.interval = 10;
1815
},
1916
afterEach: function() {
2017
this.sandbox.restore();
2118
jQuery.fx.stop();
2219
jQuery.fx.interval = this._oldInterval;
23-
window.requestAnimationFrame = oldRaf;
2420
return moduleTeardown.apply( this, arguments );
2521
}
2622
} );

0 commit comments

Comments
 (0)