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

Commit 4c4537e

Browse files
committed
perf($animate): use rAF instead of timeouts to issue animation callbacks
1 parent 6276142 commit 4c4537e

File tree

7 files changed

+143
-126
lines changed

7 files changed

+143
-126
lines changed

src/ng/animate.js

+10-6
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,11 @@ var $AnimateProvider = ['$provide', function($provide) {
8181
return this.$$classNameFilter;
8282
};
8383

84-
this.$get = ['$timeout', function($timeout) {
84+
this.$get = ['$timeout', '$$asyncCallback', function($timeout, $$asyncCallback) {
85+
86+
function async(fn) {
87+
fn && $$asyncCallback(fn);
88+
}
8589

8690
/**
8791
*
@@ -126,7 +130,7 @@ var $AnimateProvider = ['$provide', function($provide) {
126130
}
127131
parent.append(element);
128132
}
129-
done && $timeout(done, 0, false);
133+
async(done);
130134
},
131135

132136
/**
@@ -142,7 +146,7 @@ var $AnimateProvider = ['$provide', function($provide) {
142146
*/
143147
leave : function(element, done) {
144148
element.remove();
145-
done && $timeout(done, 0, false);
149+
async(done);
146150
},
147151

148152
/**
@@ -189,7 +193,7 @@ var $AnimateProvider = ['$provide', function($provide) {
189193
forEach(element, function (element) {
190194
jqLiteAddClass(element, className);
191195
});
192-
done && $timeout(done, 0, false);
196+
async(done);
193197
},
194198

195199
/**
@@ -212,7 +216,7 @@ var $AnimateProvider = ['$provide', function($provide) {
212216
forEach(element, function (element) {
213217
jqLiteRemoveClass(element, className);
214218
});
215-
done && $timeout(done, 0, false);
219+
async(done);
216220
},
217221

218222
/**
@@ -234,7 +238,7 @@ var $AnimateProvider = ['$provide', function($provide) {
234238
jqLiteAddClass(element, add);
235239
jqLiteRemoveClass(element, remove);
236240
});
237-
done && $timeout(done, 0, false);
241+
async(done);
238242
},
239243

240244
enabled : noop

src/ngAnimate/animate.js

+18-36
Original file line numberDiff line numberDiff line change
@@ -247,42 +247,24 @@ angular.module('ngAnimate', ['ng'])
247247
* Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application.
248248
*
249249
*/
250-
.factory('$$animateReflow', ['$window', '$timeout', '$document',
251-
function($window, $timeout, $document) {
250+
251+
//this private service is only used within CSS-enabled animations
252+
//IE8 + IE9 do not support rAF natively, but that is fine since they
253+
//also don't support transitions and keyframes which means that the code
254+
//below will never be used by the two browsers.
255+
.factory('$$animateReflow', ['$$rAF', '$document', function($$rAF, $document) {
252256
var bod = $document[0].body;
253-
var requestAnimationFrame = $window.requestAnimationFrame ||
254-
$window.webkitRequestAnimationFrame ||
255-
function(fn) {
256-
return $timeout(fn, 10, false);
257-
};
258-
259-
var cancelAnimationFrame = $window.cancelAnimationFrame ||
260-
$window.webkitCancelAnimationFrame ||
261-
function(timer) {
262-
return $timeout.cancel(timer);
263-
};
264257
return function(fn) {
265-
var id = requestAnimationFrame(function() {
258+
//the returned function acts as the cancellation function
259+
return $$rAF(function() {
260+
//the line below will force the browser to perform a repaint
261+
//so that all the animated elements within the animation frame
262+
//will be properly updated and drawn on screen. This is
263+
//required to perform multi-class CSS based animations with
264+
//Firefox. DO NOT REMOVE THIS LINE.
266265
var a = bod.offsetWidth + 1;
267266
fn();
268267
});
269-
return function() {
270-
cancelAnimationFrame(id);
271-
};
272-
};
273-
}])
274-
275-
.factory('$$asyncQueueBuffer', ['$timeout', function($timeout) {
276-
var timer, queue = [];
277-
return function(fn) {
278-
$timeout.cancel(timer);
279-
queue.push(fn);
280-
timer = $timeout(function() {
281-
for(var i = 0; i < queue.length; i++) {
282-
queue[i]();
283-
}
284-
queue = [];
285-
}, 0, false);
286268
};
287269
}])
288270

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

316-
$provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$$asyncQueueBuffer', '$rootScope', '$document',
317-
function($delegate, $injector, $sniffer, $rootElement, $$asyncQueueBuffer, $rootScope, $document) {
298+
$provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document',
299+
function($delegate, $injector, $sniffer, $rootElement, $$asyncCallback, $rootScope, $document) {
318300

319301
var globalAnimationCounter = 0;
320302
$rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
@@ -876,7 +858,7 @@ angular.module('ngAnimate', ['ng'])
876858
function fireDOMCallback(animationPhase) {
877859
var eventName = '$animate:' + animationPhase;
878860
if(elementEvents && elementEvents[eventName] && elementEvents[eventName].length > 0) {
879-
$$asyncQueueBuffer(function() {
861+
$$asyncCallback(function() {
880862
element.triggerHandler(eventName, {
881863
event : animationEvent,
882864
className : className
@@ -896,7 +878,7 @@ angular.module('ngAnimate', ['ng'])
896878
function fireDoneCallbackAsync() {
897879
fireDOMCallback('close');
898880
if(doneCallback) {
899-
$$asyncQueueBuffer(function() {
881+
$$asyncCallback(function() {
900882
doneCallback();
901883
});
902884
}
@@ -923,7 +905,7 @@ angular.module('ngAnimate', ['ng'])
923905
if(isClassBased) {
924906
cleanup(element, className);
925907
} else {
926-
$$asyncQueueBuffer(function() {
908+
$$asyncCallback(function() {
927909
var data = element.data(NG_ANIMATE_STATE) || {};
928910
if(localAnimationCount == data.index) {
929911
cleanup(element, className, animationEvent);

src/ngMock/angular-mocks.js

+9-6
Original file line numberDiff line numberDiff line change
@@ -757,21 +757,24 @@ angular.mock.TzDate.prototype = Date.prototype;
757757
angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
758758

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

761+
var reflowQueue = [];
762762
$provide.value('$$animateReflow', function(fn) {
763+
var index = reflowQueue.length;
763764
reflowQueue.push(fn);
764-
return angular.noop;
765+
return function cancel() {
766+
reflowQueue.splice(index, 1);
767+
};
765768
});
766769

767-
$provide.decorator('$animate', function($delegate) {
770+
$provide.decorator('$animate', function($delegate, $$asyncCallback) {
768771
var animate = {
769772
queue : [],
770773
enabled : $delegate.enabled,
774+
triggerCallbacks : function() {
775+
$$asyncCallback.flush();
776+
},
771777
triggerReflow : function() {
772-
if(reflowQueue.length === 0) {
773-
throw new Error('No animation reflows present');
774-
}
775778
angular.forEach(reflowQueue, function(fn) {
776779
fn();
777780
});

test/ng/directive/ngClassSpec.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ describe('ngClass animations', function() {
345345
//mocks are not used since the enter delegation method is called before addClass and
346346
//it makes it impossible to test to see that addClass is called first
347347
module('ngAnimate');
348+
module('ngAnimateMock');
348349

349350
var digestQueue = [];
350351
module(function($animateProvider) {
@@ -367,7 +368,7 @@ describe('ngClass animations', function() {
367368
};
368369
};
369370
});
370-
inject(function($compile, $rootScope, $rootElement, $animate, $timeout, $document) {
371+
inject(function($compile, $rootScope, $browser, $rootElement, $animate, $timeout, $document) {
371372

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

412413
expect(element.data('state')).toBe('crazy-enter');
413414
expect(enterComplete).toBe(true);

test/ng/directive/ngIncludeSpec.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ describe('ngInclude', function() {
368368

369369
expect(autoScrollSpy).not.toHaveBeenCalled();
370370
expect($animate.queue.shift().event).toBe('enter');
371-
$timeout.flush();
371+
$animate.triggerCallbacks();
372372

373373
expect(autoScrollSpy).toHaveBeenCalledOnce();
374374
}));
@@ -385,7 +385,7 @@ describe('ngInclude', function() {
385385
});
386386

387387
expect($animate.queue.shift().event).toBe('enter');
388-
$timeout.flush();
388+
$animate.triggerCallbacks();
389389

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

395395
expect($animate.queue.shift().event).toBe('leave');
396396
expect($animate.queue.shift().event).toBe('enter');
397-
$timeout.flush();
397+
$animate.triggerCallbacks();
398398

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

404404
expect($animate.queue.shift().event).toBe('leave');
405405
expect($animate.queue.shift().event).toBe('enter');
406-
$timeout.flush();
406+
$animate.triggerCallbacks();
407407

408408
expect(autoScrollSpy).toHaveBeenCalled();
409409
expect(autoScrollSpy.callCount).toBe(3);
@@ -419,7 +419,7 @@ describe('ngInclude', function() {
419419
});
420420

421421
expect($animate.queue.shift().event).toBe('enter');
422-
$timeout.flush();
422+
$animate.triggerCallbacks();
423423
expect(autoScrollSpy).not.toHaveBeenCalled();
424424
}));
425425

@@ -435,7 +435,7 @@ describe('ngInclude', function() {
435435
});
436436

437437
expect($animate.queue.shift().event).toBe('enter');
438-
$timeout.flush();
438+
$animate.triggerCallbacks();
439439

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

458458
$rootScope.$apply("tpl = 'template.html'");
459459
expect($animate.queue.shift().event).toBe('enter');
460-
$timeout.flush();
460+
$animate.triggerCallbacks();
461461

462462
expect(autoScrollSpy).toHaveBeenCalledOnce();
463463
}));

0 commit comments

Comments
 (0)