mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-05-21 12:51:51 +00:00
fix($animate): perform internal caching on getComputedStyle to boost the performance of CSS3 transitions/animations
Closes #4011 Closes #4124
This commit is contained in:
parent
1438f1b626
commit
b1e604e38c
2 changed files with 118 additions and 48 deletions
|
|
@ -642,6 +642,10 @@ angular.module('ngAnimate', ['ng'])
|
||||||
animationIterationCountKey = 'IterationCount',
|
animationIterationCountKey = 'IterationCount',
|
||||||
ELEMENT_NODE = 1;
|
ELEMENT_NODE = 1;
|
||||||
|
|
||||||
|
var NG_ANIMATE_PARENT_KEY = '$ngAnimateKey';
|
||||||
|
var lookupCache = {};
|
||||||
|
var parentCounter = 0;
|
||||||
|
|
||||||
var animationReflowQueue = [], animationTimer, timeOut = false;
|
var animationReflowQueue = [], animationTimer, timeOut = false;
|
||||||
function afterReflow(callback) {
|
function afterReflow(callback) {
|
||||||
animationReflowQueue.push(callback);
|
animationReflowQueue.push(callback);
|
||||||
|
|
@ -652,65 +656,93 @@ angular.module('ngAnimate', ['ng'])
|
||||||
});
|
});
|
||||||
animationReflowQueue = [];
|
animationReflowQueue = [];
|
||||||
animationTimer = null;
|
animationTimer = null;
|
||||||
|
lookupCache = {};
|
||||||
}, 10, false);
|
}, 10, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function animate(element, className, done) {
|
function getElementAnimationDetails(element, cacheKey, onlyCheckTransition) {
|
||||||
if(['ng-enter','ng-leave','ng-move'].indexOf(className) == -1) {
|
var data = lookupCache[cacheKey];
|
||||||
var existingDuration = 0;
|
if(!data) {
|
||||||
|
var transitionDuration = 0, transitionDelay = 0,
|
||||||
|
animationDuration = 0, animationDelay = 0;
|
||||||
|
|
||||||
|
//we want all the styles defined before and after
|
||||||
forEach(element, function(element) {
|
forEach(element, function(element) {
|
||||||
if (element.nodeType == ELEMENT_NODE) {
|
if (element.nodeType == ELEMENT_NODE) {
|
||||||
var elementStyles = $window.getComputedStyle(element) || {};
|
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) {
|
data = {
|
||||||
done();
|
transitionDelay : transitionDelay,
|
||||||
return;
|
animationDelay : animationDelay,
|
||||||
}
|
transitionDuration : transitionDuration,
|
||||||
|
animationDuration : animationDuration
|
||||||
|
};
|
||||||
|
lookupCache[cacheKey] = data;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
function animate(element, className, done) {
|
||||||
|
|
||||||
|
var cacheKey = getCacheKey(element);
|
||||||
|
if(getElementAnimationDetails(element, cacheKey, true).transitionDuration > 0) {
|
||||||
|
|
||||||
|
done();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
element.addClass(className);
|
element.addClass(className);
|
||||||
|
|
||||||
//we want all the styles defined before and after
|
var timings = getElementAnimationDetails(element, cacheKey + ' ' + className);
|
||||||
var transitionDuration = 0,
|
|
||||||
animationDuration = 0,
|
|
||||||
transitionDelay = 0,
|
|
||||||
animationDelay = 0;
|
|
||||||
forEach(element, function(element) {
|
|
||||||
if (element.nodeType == ELEMENT_NODE) {
|
|
||||||
var elementStyles = $window.getComputedStyle(element) || {};
|
|
||||||
|
|
||||||
transitionDelay = Math.max(parseMaxTime(elementStyles[transitionProp + delayKey]), transitionDelay);
|
|
||||||
|
|
||||||
animationDelay = Math.max(parseMaxTime(elementStyles[animationProp + delayKey]), animationDelay);
|
|
||||||
|
|
||||||
transitionDuration = Math.max(parseMaxTime(elementStyles[transitionProp + durationKey]), transitionDuration);
|
|
||||||
|
|
||||||
var aDuration = parseMaxTime(elementStyles[animationProp + durationKey]);
|
|
||||||
|
|
||||||
if(aDuration > 0) {
|
|
||||||
aDuration *= parseInt(elementStyles[animationProp + animationIterationCountKey]) || 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
animationDuration = Math.max(aDuration, animationDuration);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* there is no point in performing a reflow if the animation
|
/* there is no point in performing a reflow if the animation
|
||||||
timeout is empty (this would cause a flicker bug normally
|
timeout is empty (this would cause a flicker bug normally
|
||||||
in the page. There is also no point in performing an animation
|
in the page. There is also no point in performing an animation
|
||||||
that only has a delay and no duration */
|
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) {
|
if(maxDuration > 0) {
|
||||||
var maxDelayTime = Math.max(transitionDelay, animationDelay) * 1000,
|
var maxDelayTime = Math.max(timings.transitionDelay, timings.animationDelay) * 1000,
|
||||||
startTime = Date.now(),
|
startTime = Date.now(),
|
||||||
node = element[0];
|
node = element[0];
|
||||||
|
|
||||||
//temporarily disable the transition so that the enter styles
|
//temporarily disable the transition so that the enter styles
|
||||||
//don't animate twice (this is here to avoid a bug in Chrome/FF).
|
//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';
|
node.style[transitionProp + propertyKey] = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -723,7 +755,7 @@ angular.module('ngAnimate', ['ng'])
|
||||||
var css3AnimationEvents = animationendEvent + ' ' + transitionendEvent;
|
var css3AnimationEvents = animationendEvent + ' ' + transitionendEvent;
|
||||||
|
|
||||||
afterReflow(function() {
|
afterReflow(function() {
|
||||||
if(transitionDuration > 0) {
|
if(timings.transitionDuration > 0) {
|
||||||
node.style[transitionProp + propertyKey] = '';
|
node.style[transitionProp + propertyKey] = '';
|
||||||
}
|
}
|
||||||
element.addClass(activeClassName);
|
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 {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -726,10 +726,10 @@ describe("ngAnimate", function() {
|
||||||
it('should re-evaluate the CSS classes for an animation each time',
|
it('should re-evaluate the CSS classes for an animation each time',
|
||||||
inject(function($animate, $rootScope, $sniffer, $rootElement, $timeout, $compile) {
|
inject(function($animate, $rootScope, $sniffer, $rootElement, $timeout, $compile) {
|
||||||
|
|
||||||
ss.addRule('.abc', '-webkit-transition:22s linear all;' +
|
ss.addRule('.abc.ng-enter', '-webkit-transition:22s linear all;' +
|
||||||
'transition:22s linear all;');
|
'transition:22s linear all;');
|
||||||
ss.addRule('.xyz', '-webkit-transition:11s linear all;' +
|
ss.addRule('.xyz.ng-enter', '-webkit-transition:11s linear all;' +
|
||||||
'transition:11s linear all;');
|
'transition:11s linear all;');
|
||||||
|
|
||||||
var parent = $compile('<div><span ng-class="klass"></span></div>')($rootScope);
|
var parent = $compile('<div><span ng-class="klass"></span></div>')($rootScope);
|
||||||
var element = parent.find('span');
|
var element = parent.find('span');
|
||||||
|
|
@ -1875,4 +1875,49 @@ describe("ngAnimate", function() {
|
||||||
expect(intercepted).toBe(true);
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue