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

Commit

Permalink
perf($animate): use rAF instead of timeouts to issue animation callbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
matsko committed Feb 25, 2014
1 parent 6276142 commit 4c4537e
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 126 deletions.
16 changes: 10 additions & 6 deletions src/ng/animate.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,11 @@ var $AnimateProvider = ['$provide', function($provide) {
return this.$$classNameFilter;
};

this.$get = ['$timeout', function($timeout) {
this.$get = ['$timeout', '$$asyncCallback', function($timeout, $$asyncCallback) {

function async(fn) {
fn && $$asyncCallback(fn);
}

/**
*
Expand Down Expand Up @@ -126,7 +130,7 @@ var $AnimateProvider = ['$provide', function($provide) {
}
parent.append(element);
}
done && $timeout(done, 0, false);
async(done);
},

/**
Expand All @@ -142,7 +146,7 @@ var $AnimateProvider = ['$provide', function($provide) {
*/
leave : function(element, done) {
element.remove();
done && $timeout(done, 0, false);
async(done);
},

/**
Expand Down Expand Up @@ -189,7 +193,7 @@ var $AnimateProvider = ['$provide', function($provide) {
forEach(element, function (element) {
jqLiteAddClass(element, className);
});
done && $timeout(done, 0, false);
async(done);
},

/**
Expand All @@ -212,7 +216,7 @@ var $AnimateProvider = ['$provide', function($provide) {
forEach(element, function (element) {
jqLiteRemoveClass(element, className);
});
done && $timeout(done, 0, false);
async(done);
},

/**
Expand All @@ -234,7 +238,7 @@ var $AnimateProvider = ['$provide', function($provide) {
jqLiteAddClass(element, add);
jqLiteRemoveClass(element, remove);
});
done && $timeout(done, 0, false);
async(done);
},

enabled : noop
Expand Down
54 changes: 18 additions & 36 deletions src/ngAnimate/animate.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,42 +247,24 @@ 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', '$document',
function($window, $timeout, $document) {

//this private service is only used within CSS-enabled animations
//IE8 + IE9 do not support rAF natively, but that is fine since they
//also don't support transitions and keyframes which means that the code
//below will never be used by the two browsers.
.factory('$$animateReflow', ['$$rAF', '$document', function($$rAF, $document) {
var bod = $document[0].body;
var requestAnimationFrame = $window.requestAnimationFrame ||
$window.webkitRequestAnimationFrame ||
function(fn) {
return $timeout(fn, 10, false);
};

var cancelAnimationFrame = $window.cancelAnimationFrame ||
$window.webkitCancelAnimationFrame ||
function(timer) {
return $timeout.cancel(timer);
};
return function(fn) {
var id = requestAnimationFrame(function() {
//the returned function acts as the cancellation function
return $$rAF(function() {
//the line below will force the browser to perform a repaint
//so that all the animated elements within the animation frame
//will be properly updated and drawn on screen. This is
//required to perform multi-class CSS based animations with
//Firefox. DO NOT REMOVE THIS LINE.
var a = bod.offsetWidth + 1;
fn();
});
return function() {
cancelAnimationFrame(id);
};
};
}])

.factory('$$asyncQueueBuffer', ['$timeout', function($timeout) {
var timer, queue = [];
return function(fn) {
$timeout.cancel(timer);
queue.push(fn);
timer = $timeout(function() {
for(var i = 0; i < queue.length; i++) {
queue[i]();
}
queue = [];
}, 0, false);
};
}])

Expand Down Expand Up @@ -313,8 +295,8 @@ angular.module('ngAnimate', ['ng'])
return extractElementNode(elm1) == extractElementNode(elm2);
}

$provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$$asyncQueueBuffer', '$rootScope', '$document',
function($delegate, $injector, $sniffer, $rootElement, $$asyncQueueBuffer, $rootScope, $document) {
$provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document',
function($delegate, $injector, $sniffer, $rootElement, $$asyncCallback, $rootScope, $document) {

var globalAnimationCounter = 0;
$rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
Expand Down Expand Up @@ -876,7 +858,7 @@ angular.module('ngAnimate', ['ng'])
function fireDOMCallback(animationPhase) {
var eventName = '$animate:' + animationPhase;
if(elementEvents && elementEvents[eventName] && elementEvents[eventName].length > 0) {
$$asyncQueueBuffer(function() {
$$asyncCallback(function() {
element.triggerHandler(eventName, {
event : animationEvent,
className : className
Expand All @@ -896,7 +878,7 @@ angular.module('ngAnimate', ['ng'])
function fireDoneCallbackAsync() {
fireDOMCallback('close');
if(doneCallback) {
$$asyncQueueBuffer(function() {
$$asyncCallback(function() {
doneCallback();
});
}
Expand All @@ -923,7 +905,7 @@ angular.module('ngAnimate', ['ng'])
if(isClassBased) {
cleanup(element, className);
} else {
$$asyncQueueBuffer(function() {
$$asyncCallback(function() {
var data = element.data(NG_ANIMATE_STATE) || {};
if(localAnimationCount == data.index) {
cleanup(element, className, animationEvent);
Expand Down
15 changes: 9 additions & 6 deletions src/ngMock/angular-mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -757,21 +757,24 @@ angular.mock.TzDate.prototype = Date.prototype;
angular.mock.animate = angular.module('ngAnimateMock', ['ng'])

.config(['$provide', function($provide) {
var reflowQueue = [];

var reflowQueue = [];
$provide.value('$$animateReflow', function(fn) {
var index = reflowQueue.length;
reflowQueue.push(fn);
return angular.noop;
return function cancel() {
reflowQueue.splice(index, 1);
};
});

$provide.decorator('$animate', function($delegate) {
$provide.decorator('$animate', function($delegate, $$asyncCallback) {
var animate = {
queue : [],
enabled : $delegate.enabled,
triggerCallbacks : function() {
$$asyncCallback.flush();
},
triggerReflow : function() {
if(reflowQueue.length === 0) {
throw new Error('No animation reflows present');
}
angular.forEach(reflowQueue, function(fn) {
fn();
});
Expand Down
5 changes: 3 additions & 2 deletions test/ng/directive/ngClassSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ describe('ngClass animations', function() {
//mocks are not used since the enter delegation method is called before addClass and
//it makes it impossible to test to see that addClass is called first
module('ngAnimate');
module('ngAnimateMock');

var digestQueue = [];
module(function($animateProvider) {
Expand All @@ -367,7 +368,7 @@ describe('ngClass animations', function() {
};
};
});
inject(function($compile, $rootScope, $rootElement, $animate, $timeout, $document) {
inject(function($compile, $rootScope, $browser, $rootElement, $animate, $timeout, $document) {

// Enable animations by triggering the first item in the postDigest queue
digestQueue.shift()();
Expand Down Expand Up @@ -407,7 +408,7 @@ describe('ngClass animations', function() {
//is spaced-out then it is required so that the original digestion
//is kicked into gear
$rootScope.$digest();
$timeout.flush();
$animate.triggerCallbacks();

expect(element.data('state')).toBe('crazy-enter');
expect(enterComplete).toBe(true);
Expand Down
14 changes: 7 additions & 7 deletions test/ng/directive/ngIncludeSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ describe('ngInclude', function() {

expect(autoScrollSpy).not.toHaveBeenCalled();
expect($animate.queue.shift().event).toBe('enter');
$timeout.flush();
$animate.triggerCallbacks();

expect(autoScrollSpy).toHaveBeenCalledOnce();
}));
Expand All @@ -385,7 +385,7 @@ describe('ngInclude', function() {
});

expect($animate.queue.shift().event).toBe('enter');
$timeout.flush();
$animate.triggerCallbacks();

$rootScope.$apply(function () {
$rootScope.tpl = 'another.html';
Expand All @@ -394,7 +394,7 @@ describe('ngInclude', function() {

expect($animate.queue.shift().event).toBe('leave');
expect($animate.queue.shift().event).toBe('enter');
$timeout.flush();
$animate.triggerCallbacks();

$rootScope.$apply(function() {
$rootScope.tpl = 'template.html';
Expand All @@ -403,7 +403,7 @@ describe('ngInclude', function() {

expect($animate.queue.shift().event).toBe('leave');
expect($animate.queue.shift().event).toBe('enter');
$timeout.flush();
$animate.triggerCallbacks();

expect(autoScrollSpy).toHaveBeenCalled();
expect(autoScrollSpy.callCount).toBe(3);
Expand All @@ -419,7 +419,7 @@ describe('ngInclude', function() {
});

expect($animate.queue.shift().event).toBe('enter');
$timeout.flush();
$animate.triggerCallbacks();
expect(autoScrollSpy).not.toHaveBeenCalled();
}));

Expand All @@ -435,7 +435,7 @@ describe('ngInclude', function() {
});

expect($animate.queue.shift().event).toBe('enter');
$timeout.flush();
$animate.triggerCallbacks();

$rootScope.$apply(function () {
$rootScope.tpl = 'template.html';
Expand All @@ -457,7 +457,7 @@ describe('ngInclude', function() {

$rootScope.$apply("tpl = 'template.html'");
expect($animate.queue.shift().event).toBe('enter');
$timeout.flush();
$animate.triggerCallbacks();

expect(autoScrollSpy).toHaveBeenCalledOnce();
}));
Expand Down
Loading

0 comments on commit 4c4537e

Please sign in to comment.