From 6fd73d03c46a1c669d385cbe667eaab8ea34e02a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Go=C5=82e=CC=A8biowski-Owczarek?= Date: Wed, 8 Jun 2016 14:49:47 +0200 Subject: [PATCH] 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 --- Gruntfile.js | 1 + src/effects.js | 19 +++++++--- test/data/effects/matchingTimestamps.html | 38 +++++++++++++++++++ test/unit/animation.js | 8 ++-- test/unit/effects.js | 46 +++++++++++++++++++---- test/unit/tween.js | 4 -- 6 files changed, 93 insertions(+), 23 deletions(-) create mode 100644 test/data/effects/matchingTimestamps.html diff --git a/Gruntfile.js b/Gruntfile.js index 012ce95dba..3b6f0d476c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -108,6 +108,7 @@ module.exports = function( grunt ) { "deprecated", "dimensions", "effects", + "effects-nostubs", "event", "manipulation", "offset", diff --git a/src/effects.js b/src/effects.js index 46ad988ada..cc113683d0 100644 --- a/src/effects.js +++ b/src/effects.js @@ -27,24 +27,31 @@ var rfxtypes = /^(?:toggle|show|hide)$/, rrun = /queueHooks$/; -function schedule() { +function schedule( timestamp ) { if ( inProgress ) { - if ( document.hidden === false && window.requestAnimationFrame ) { + if ( document.hidden === false ) { window.requestAnimationFrame( schedule ); } else { window.setTimeout( schedule, jQuery.fx.interval ); } - jQuery.fx.tick(); + jQuery.fx.tick( timestamp ); } } +// We need to be using Date.now() or performance.now() consistently as they return different +// values: performance.now() counter starts on page load. +// Support: IE <10, Safari <8.0, iOS <9, Android <4.4, Node with jsdom 9.4 +function getTimestamp() { + return window.performance.now(); +} + // Animations created synchronously will run synchronously function createFxNow() { window.setTimeout( function() { fxNow = undefined; } ); - return ( fxNow = Date.now() ); + return ( fxNow = getTimestamp() ); } // Generate parameters to create a standard animation @@ -644,12 +651,12 @@ jQuery.each( { } ); jQuery.timers = []; -jQuery.fx.tick = function() { +jQuery.fx.tick = function( timestamp ) { var timer, i = 0, timers = jQuery.timers; - fxNow = Date.now(); + fxNow = timestamp || getTimestamp(); for ( ; i < timers.length; i++ ) { timer = timers[ i ]; diff --git a/test/data/effects/matchingTimestamps.html b/test/data/effects/matchingTimestamps.html new file mode 100644 index 0000000000..2677b36707 --- /dev/null +++ b/test/data/effects/matchingTimestamps.html @@ -0,0 +1,38 @@ + + + + + Effects: matching timestamps + + + + +
+ + + diff --git a/test/unit/animation.js b/test/unit/animation.js index 4af1f7f983..875c7a942d 100644 --- a/test/unit/animation.js +++ b/test/unit/animation.js @@ -5,10 +5,9 @@ if ( !jQuery.fx ) { return; } -var oldRaf = window.requestAnimationFrame, - defaultPrefilter = jQuery.Animation.prefilters[ 0 ], - defaultTweener = jQuery.Animation.tweeners[ "*" ][ 0 ], - startTime = 505877050; +var defaultPrefilter = jQuery.Animation.prefilters[ 0 ]; +var defaultTweener = jQuery.Animation.tweeners[ "*" ][ 0 ]; +var startTime = 505877050; // This module tests jQuery.Animation and the corresponding 1.8+ effects APIs QUnit.module( "animation", { @@ -26,7 +25,6 @@ QUnit.module( "animation", { this.sandbox.restore(); jQuery.fx.stop(); jQuery.fx.interval = this._oldInterval; - window.requestAnimationFrame = oldRaf; return moduleTeardown.apply( this, arguments ); } } ); diff --git a/test/unit/effects.js b/test/unit/effects.js index 13190dc5bb..7b20b34774 100644 --- a/test/unit/effects.js +++ b/test/unit/effects.js @@ -5,29 +5,42 @@ if ( !jQuery.fx ) { return; } -var oldRaf = window.requestAnimationFrame, - hideOptions = { - inline: function() { jQuery.style( this, "display", "none" ); }, - cascade: function() { this.className = "hidden"; } - }; +var hideOptions = { + inline: function() { jQuery.style( this, "display", "none" ); }, + cascade: function() { this.className = "hidden"; } +}; QUnit.module( "effects", { beforeEach: function() { this.sandbox = sinon.createSandbox(); this.clock = this.sandbox.useFakeTimers( 505877050 ); + this._oldInterval = jQuery.fx.interval; - window.requestAnimationFrame = null; jQuery.fx.step = {}; jQuery.fx.interval = 10; + + // Simulate rAF using the mocked setTimeout as otherwise we couldn't test + // the rAF + performance.now code path. + this.sandbox + .stub( window, "requestAnimationFrame" ) + .callsFake( function( callback ) { + return window.setTimeout( callback, jQuery.fx.interval, + Date.now() - 99989.6394 ); + } ); + + // Fake performance.now() returning lower values than Date.now() + // and that its values are fractional. + this.sandbox.stub( window.performance, "now" ).callsFake( function() { + return Date.now() - 99999.6394; + } ); }, afterEach: function() { this.sandbox.restore(); jQuery.fx.stop(); jQuery.fx.interval = this._oldInterval; - window.requestAnimationFrame = oldRaf; return moduleTeardown.apply( this, arguments ); } -} ); +}, function() { QUnit[ QUnit.jQuerySelectors ? "test" : "skip" ]( "sanity check", function( assert ) { assert.expect( 1 ); @@ -2623,4 +2636,21 @@ QUnit.test( "jQuery.speed() - durations", function( assert ) { jQuery.fx.off = false; } ); +} ); + +QUnit.module( "effects-nostubs", function() { + +testIframe( + "matching timestamp", + "effects/matchingTimestamps.html", + function( assert, framejQuery, frameWin, frameDoc, success, failureReason ) { + assert.expect( 1 ); + + assert.ok( success, failureReason ); + } +); + +} ); + } )(); + diff --git a/test/unit/tween.js b/test/unit/tween.js index 9367978774..2e77436c99 100644 --- a/test/unit/tween.js +++ b/test/unit/tween.js @@ -5,14 +5,11 @@ if ( !jQuery.fx ) { return; } -var oldRaf = window.requestAnimationFrame; - QUnit.module( "tween", { beforeEach: function() { this.sandbox = sinon.createSandbox(); this.clock = this.sandbox.useFakeTimers( 505877050 ); this._oldInterval = jQuery.fx.interval; - window.requestAnimationFrame = null; jQuery.fx.step = {}; jQuery.fx.interval = 10; }, @@ -20,7 +17,6 @@ QUnit.module( "tween", { this.sandbox.restore(); jQuery.fx.stop(); jQuery.fx.interval = this._oldInterval; - window.requestAnimationFrame = oldRaf; return moduleTeardown.apply( this, arguments ); } } );