Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
feat($animate): use requestAnimationFrame instead of a timeout to iss…
Browse files Browse the repository at this point in the history
…ue a reflow

Closes #4278
Closes #4225
  • Loading branch information
matsko committed Jan 14, 2014
1 parent ed53100 commit 4ae3184
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 82 deletions.
2 changes: 1 addition & 1 deletion karma-docs.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ module.exports = function(config) {

'build/angular.js',
'build/angular-cookies.js',
'build/angular-mocks.js',
'build/angular-resource.js',
'build/angular-touch.js',
'build/angular-sanitize.js',
'build/angular-route.js',
'build/angular-animate.js',
'build/angular-mocks.js',

'build/docs/components/lunr.js',
'build/docs/components/google-code-prettify.js',
Expand Down
37 changes: 31 additions & 6 deletions src/ngAnimate/animate.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,28 @@ angular.module('ngAnimate', ['ng'])
* Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application.
*
*/
.factory('$$animateReflow', ['$window', '$timeout', function($window, $timeout) {
var requestAnimationFrame = $window.requestAnimationFrame ||
$window.mozRequestAnimationFrame ||

This comment has been minimized.

Copy link
@mgol

mgol Jan 15, 2014

Member

This has not been needed since Firefox 23 (current version is 26). I'd drop that.

This comment has been minimized.

Copy link
@matsko

matsko Jan 15, 2014

Author Contributor

Fixed. Thanks.

$window.webkitRequestAnimationFrame ||
function(fn) {
return $timeout(fn, 10, false);
};

This comment has been minimized.

Copy link
@mgol

mgol Jan 15, 2014

Member

I don't know how important non-raf browsers are to Angular but a much better polyfill would be (this is not mine but it's quite good):

function (callback) {
    const currTime = Date.now();
    const timeToCall = Math.max(0, 16 - (currTime - lastTime));
    const id = $timeout(
        function () {
            callback(currTime + timeToCall);
        },
        timeToCall,
        false);
    lastTime = currTime + timeToCall;
    return id;
};

This takes into account the time that callback has taken and it aims at 60 fps (hence 16 ms timeout, 10 ms is needlessly small).

This comment has been minimized.

Copy link
@mgol

mgol Jan 15, 2014

Member

This would mostly matter for the Android browser (before Chrome-based 4.4 there is no rAF at all) and iOS 5.1 that is the last one on the first iPad. Also, IE<10.

This comment has been minimized.

Copy link
@matsko

matsko Jan 15, 2014

Author Contributor

This actually isn't an approach to best emulate RAF. It's just a fallback to ensure that the animations get grouped together properly once again (this is the same code as was done before RAF was added). It allows for $animate to figure out how many elements are being animated and then prepare a stagger delay. Also it will get cancelled each time a new animation kicks in. I can experiment more with how well it works once 1.2.9 is done.


var cancelAnimationFrame = $window.cancelAnimationFrame ||
$window.mozCancelAnimationFrame ||

This comment has been minimized.

Copy link
@mgol

mgol Jan 15, 2014

Member

see above

$window.webkitCancelAnimationFrame ||
function(timer) {
return $timeout.cancel(timer);
};
return function(fn) {
var id = requestAnimationFrame(fn);
return function() {
cancelAnimationFrame(id);
};
};
}])

.config(['$provide', '$animateProvider', function($provide, $animateProvider) {
var noop = angular.noop;
var forEach = angular.forEach;
Expand Down Expand Up @@ -872,7 +894,8 @@ angular.module('ngAnimate', ['ng'])
}
}]);

$animateProvider.register('', ['$window', '$sniffer', '$timeout', function($window, $sniffer, $timeout) {
$animateProvider.register('', ['$window', '$sniffer', '$timeout', '$$animateReflow',
function($window, $sniffer, $timeout, $$animateReflow) {
// Detect proper transitionend/animationend event names.
var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT;

Expand Down Expand Up @@ -917,11 +940,13 @@ angular.module('ngAnimate', ['ng'])
var parentCounter = 0;
var animationReflowQueue = [];
var animationElementQueue = [];
var animationTimer;
var cancelAnimationReflow;
var closingAnimationTime = 0;
var timeOut = false;
function afterReflow(element, callback) {
$timeout.cancel(animationTimer);
if(cancelAnimationReflow) {
cancelAnimationReflow();
}

animationReflowQueue.push(callback);

Expand All @@ -942,7 +967,7 @@ angular.module('ngAnimate', ['ng'])
//a follow-up animation is midway in its animation
elementData.animationCount = animationCounter;

animationTimer = $timeout(function() {
cancelAnimationReflow = $$animateReflow(function() {
forEach(animationReflowQueue, function(fn) {
fn();
});
Expand All @@ -963,11 +988,11 @@ angular.module('ngAnimate', ['ng'])

animationReflowQueue = [];
animationElementQueue = [];
animationTimer = null;
cancelAnimationReflow = null;
lookupCache = {};
closingAnimationTime = 0;
animationCounter++;
}, 10, false);
});
}

function closeAllAnimations(elements, count) {
Expand Down
21 changes: 20 additions & 1 deletion src/ngMock/angular-mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,26 @@ angular.mock.TzDate = function (offset, timestamp) {
angular.mock.TzDate.prototype = Date.prototype;
/* jshint +W101 */

angular.module('ngAnimate').config(['$provide', function($provide) {

This comment has been minimized.

Copy link
@IgorMinar

IgorMinar Jan 15, 2014

Contributor

this line means that ngAnimate modules must be loaded for each test now.

that's wrong. we should implement this differently.

var reflowQueue = [];
$provide.value('$$animateReflow', function(fn) {
reflowQueue.push(fn);
return angular.noop;
});
$provide.decorator('$animate', function($delegate) {
$delegate.triggerReflow = function() {
if(reflowQueue.length === 0) {
throw new Error('No animation reflows present');
}
angular.forEach(reflowQueue, function(fn) {
fn();
});
reflowQueue = [];
};
return $delegate;
});
}]);

angular.mock.animate = angular.module('mock.animate', ['ng'])

.config(['$provide', function($provide) {
Expand Down Expand Up @@ -1913,7 +1933,6 @@ angular.mock.clearDataCache = function() {
};



if(window.jasmine || window.mocha) {

var currentSpec = null,
Expand Down
Loading

0 comments on commit 4ae3184

Please sign in to comment.