Skip to content

Commit 78e6282

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 b220f6d commit 78e6282

File tree

6 files changed

+95
-25
lines changed

6 files changed

+95
-25
lines changed

Gruntfile.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ module.exports = function( grunt ) {
131131
"deprecated",
132132
"dimensions",
133133
"effects",
134+
"effects-nostubs",
134135
"event",
135136
"manipulation",
136137
"offset",

src/effects.js

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ define( [
1818
"./manipulation",
1919
"./css",
2020
"./effects/Tween"
21-
], function( jQuery, document, rcssNum, rnothtmlwhite, cssExpand,
22-
isHiddenWithinTree, swap, adjustCSS, cssCamelCase, dataPriv, showHide ) {
21+
], function( jQuery, document, rcssNum, rnothtmlwhite, cssExpand, isHiddenWithinTree,
22+
swap, adjustCSS, cssCamelCase, dataPriv, showHide ) {
2323

2424
"use strict";
2525

@@ -28,24 +28,31 @@ var
2828
rfxtypes = /^(?:toggle|show|hide)$/,
2929
rrun = /queueHooks$/;
3030

31-
function schedule() {
31+
function schedule( timestamp ) {
3232
if ( inProgress ) {
33-
if ( document.hidden === false && window.requestAnimationFrame ) {
33+
if ( document.hidden === false ) {
3434
window.requestAnimationFrame( schedule );
3535
} else {
3636
window.setTimeout( schedule, jQuery.fx.interval );
3737
}
3838

39-
jQuery.fx.tick();
39+
jQuery.fx.tick( timestamp );
4040
}
4141
}
4242

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

5158
// Generate parameters to create a standard animation
@@ -645,12 +652,12 @@ jQuery.each( {
645652
} );
646653

647654
jQuery.timers = [];
648-
jQuery.fx.tick = function() {
655+
jQuery.fx.tick = function( timestamp ) {
649656
var timer,
650657
i = 0,
651658
timers = jQuery.timers;
652659

653-
fxNow = Date.now();
660+
fxNow = timestamp || getTimestamp();
654661

655662
for ( ; i < timers.length; i++ ) {
656663
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[ jQuery.find.compile ? "test" : "skip" ]( "sanity check", function( assert ) {
3346
assert.expect( 1 );
@@ -2621,4 +2634,21 @@ QUnit.test( "jQuery.speed() - durations", function( assert ) {
26212634
jQuery.fx.off = false;
26222635
} );
26232636

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

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)