aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatias Niemelä2013-10-09 01:12:03 -0400
committerMisko Hevery2013-10-10 17:35:36 -0700
commitb1e604e38ceec1714174fb54cc91590a7fe99a92 (patch)
treef26d58c2e92a006beede067b0e5a42f09207b598
parent1438f1b62600ab8c4092486a1b4e5e4505565a00 (diff)
downloadangular.js-b1e604e38ceec1714174fb54cc91590a7fe99a92.tar.bz2
fix($animate): perform internal caching on getComputedStyle to boost the performance of CSS3 transitions/animations
Closes #4011 Closes #4124
-rw-r--r--src/ngAnimate/animate.js105
-rw-r--r--test/ngAnimate/animateSpec.js53
2 files changed, 114 insertions, 44 deletions
diff --git a/src/ngAnimate/animate.js b/src/ngAnimate/animate.js
index 4bd7b886..f5be6b76 100644
--- a/src/ngAnimate/animate.js
+++ b/src/ngAnimate/animate.js
@@ -642,6 +642,10 @@ angular.module('ngAnimate', ['ng'])
animationIterationCountKey = 'IterationCount',
ELEMENT_NODE = 1;
+ var NG_ANIMATE_PARENT_KEY = '$ngAnimateKey';
+ var lookupCache = {};
+ var parentCounter = 0;
+
var animationReflowQueue = [], animationTimer, timeOut = false;
function afterReflow(callback) {
animationReflowQueue.push(callback);
@@ -652,65 +656,93 @@ angular.module('ngAnimate', ['ng'])
});
animationReflowQueue = [];
animationTimer = null;
+ lookupCache = {};
}, 10, false);
}
- function animate(element, className, done) {
- if(['ng-enter','ng-leave','ng-move'].indexOf(className) == -1) {
- var existingDuration = 0;
+ function getElementAnimationDetails(element, cacheKey, onlyCheckTransition) {
+ var data = lookupCache[cacheKey];
+ if(!data) {
+ var transitionDuration = 0, transitionDelay = 0,
+ animationDuration = 0, animationDelay = 0;
+
+ //we want all the styles defined before and after
forEach(element, function(element) {
if (element.nodeType == ELEMENT_NODE) {
var elementStyles = $window.getComputedStyle(element) || {};
- existingDuration = Math.max(parseMaxTime(elementStyles[transitionProp + durationKey]),
- existingDuration);
+
+ transitionDuration = Math.max(parseMaxTime(elementStyles[transitionProp + durationKey]), transitionDuration);
+
+ if(!onlyCheckTransition) {
+ transitionDelay = Math.max(parseMaxTime(elementStyles[transitionProp + delayKey]), transitionDelay);
+
+ animationDelay = Math.max(parseMaxTime(elementStyles[animationProp + delayKey]), animationDelay);
+
+ var aDuration = parseMaxTime(elementStyles[animationProp + durationKey]);
+
+ if(aDuration > 0) {
+ aDuration *= parseInt(elementStyles[animationProp + animationIterationCountKey]) || 1;
+ }
+
+ animationDuration = Math.max(aDuration, animationDuration);
+ }
}
});
- if(existingDuration > 0) {
- done();
- return;
- }
+ data = {
+ transitionDelay : transitionDelay,
+ animationDelay : animationDelay,
+ transitionDuration : transitionDuration,
+ animationDuration : animationDuration
+ };
+ lookupCache[cacheKey] = data;
}
+ return data;
+ }
- element.addClass(className);
-
- //we want all the styles defined before and after
- var transitionDuration = 0,
- animationDuration = 0,
- transitionDelay = 0,
- animationDelay = 0;
- forEach(element, function(element) {
- if (element.nodeType == ELEMENT_NODE) {
- var elementStyles = $window.getComputedStyle(element) || {};
+ function parseMaxTime(str) {
+ var total = 0, values = angular.isString(str) ? str.split(/\s*,\s*/) : [];
+ forEach(values, function(value) {
+ total = Math.max(parseFloat(value) || 0, total);
+ });
+ return total;
+ }
- transitionDelay = Math.max(parseMaxTime(elementStyles[transitionProp + delayKey]), transitionDelay);
+ function getCacheKey(element) {
+ var parent = element.parent();
+ var parentID = parent.data(NG_ANIMATE_PARENT_KEY);
+ if(!parentID) {
+ parent.data(NG_ANIMATE_PARENT_KEY, ++parentCounter);
+ parentID = parentCounter;
+ }
+ return parentID + '-' + element[0].className;
+ }
- animationDelay = Math.max(parseMaxTime(elementStyles[animationProp + delayKey]), animationDelay);
+ function animate(element, className, done) {
- transitionDuration = Math.max(parseMaxTime(elementStyles[transitionProp + durationKey]), transitionDuration);
+ var cacheKey = getCacheKey(element);
+ if(getElementAnimationDetails(element, cacheKey, true).transitionDuration > 0) {
- var aDuration = parseMaxTime(elementStyles[animationProp + durationKey]);
+ done();
+ return;
+ }
- if(aDuration > 0) {
- aDuration *= parseInt(elementStyles[animationProp + animationIterationCountKey]) || 1;
- }
+ element.addClass(className);
- animationDuration = Math.max(aDuration, animationDuration);
- }
- });
+ var timings = getElementAnimationDetails(element, cacheKey + ' ' + className);
/* there is no point in performing a reflow if the animation
timeout is empty (this would cause a flicker bug normally
in the page. There is also no point in performing an animation
that only has a delay and no duration */
- var maxDuration = Math.max(transitionDuration, animationDuration);
+ var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
if(maxDuration > 0) {
- var maxDelayTime = Math.max(transitionDelay, animationDelay) * 1000,
+ var maxDelayTime = Math.max(timings.transitionDelay, timings.animationDelay) * 1000,
startTime = Date.now(),
node = element[0];
//temporarily disable the transition so that the enter styles
//don't animate twice (this is here to avoid a bug in Chrome/FF).
- if(transitionDuration > 0) {
+ if(timings.transitionDuration > 0) {
node.style[transitionProp + propertyKey] = 'none';
}
@@ -723,7 +755,7 @@ angular.module('ngAnimate', ['ng'])
var css3AnimationEvents = animationendEvent + ' ' + transitionendEvent;
afterReflow(function() {
- if(transitionDuration > 0) {
+ if(timings.transitionDuration > 0) {
node.style[transitionProp + propertyKey] = '';
}
element.addClass(activeClassName);
@@ -768,13 +800,6 @@ angular.module('ngAnimate', ['ng'])
}
}
- function parseMaxTime(str) {
- var total = 0, values = angular.isString(str) ? str.split(/\s*,\s*/) : [];
- forEach(values, function(value) {
- total = Math.max(parseFloat(value) || 0, total);
- });
- return total;
- }
}
return {
diff --git a/test/ngAnimate/animateSpec.js b/test/ngAnimate/animateSpec.js
index 3652e450..cae25266 100644
--- a/test/ngAnimate/animateSpec.js
+++ b/test/ngAnimate/animateSpec.js
@@ -726,10 +726,10 @@ describe("ngAnimate", function() {
it('should re-evaluate the CSS classes for an animation each time',
inject(function($animate, $rootScope, $sniffer, $rootElement, $timeout, $compile) {
- ss.addRule('.abc', '-webkit-transition:22s linear all;' +
- 'transition:22s linear all;');
- ss.addRule('.xyz', '-webkit-transition:11s linear all;' +
- 'transition:11s linear all;');
+ ss.addRule('.abc.ng-enter', '-webkit-transition:22s linear all;' +
+ 'transition:22s linear all;');
+ ss.addRule('.xyz.ng-enter', '-webkit-transition:11s linear all;' +
+ 'transition:11s linear all;');
var parent = $compile('<div><span ng-class="klass"></span></div>')($rootScope);
var element = parent.find('span');
@@ -1875,4 +1875,49 @@ describe("ngAnimate", function() {
expect(intercepted).toBe(true);
});
});
+
+ it("should cache the response from getComputedStyle if each successive element has the same className value and parent until the first reflow hits", function() {
+ var count = 0;
+ module(function($provide) {
+ $provide.value('$window', {
+ document : jqLite(window.document),
+ getComputedStyle: function(element) {
+ count++;
+ return window.getComputedStyle(element);
+ }
+ });
+ });
+
+ inject(function($animate, $rootScope, $compile, $rootElement, $timeout, $document, $sniffer) {
+ if(!$sniffer.transitions) return;
+
+ $animate.enabled(true);
+
+ var element = $compile('<div></div>')($rootScope);
+ $rootElement.append(element);
+ jqLite($document[0].body).append($rootElement);
+
+ for(var i=0;i<20;i++) {
+ var kid = $compile('<div class="kid"></div>')($rootScope);
+ $animate.enter(kid, element);
+ }
+ $rootScope.$digest();
+ $timeout.flush();
+
+ expect(count).toBe(2);
+
+ dealoc(element);
+ count = 0;
+
+ for(var i=0;i<20;i++) {
+ var kid = $compile('<div class="kid c-'+i+'"></div>')($rootScope);
+ $animate.enter(kid, element);
+ }
+
+ $rootScope.$digest();
+ $timeout.flush();
+
+ expect(count).toBe(40);
+ });
+ });
});