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

Commit b1e604e

Browse files
matskomhevery
authored andcommitted
fix($animate): perform internal caching on getComputedStyle to boost the performance of CSS3 transitions/animations
Closes #4011 Closes #4124
1 parent 1438f1b commit b1e604e

File tree

2 files changed

+114
-44
lines changed

2 files changed

+114
-44
lines changed

src/ngAnimate/animate.js

+65-40
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,10 @@ angular.module('ngAnimate', ['ng'])
642642
animationIterationCountKey = 'IterationCount',
643643
ELEMENT_NODE = 1;
644644

645+
var NG_ANIMATE_PARENT_KEY = '$ngAnimateKey';
646+
var lookupCache = {};
647+
var parentCounter = 0;
648+
645649
var animationReflowQueue = [], animationTimer, timeOut = false;
646650
function afterReflow(callback) {
647651
animationReflowQueue.push(callback);
@@ -652,65 +656,93 @@ angular.module('ngAnimate', ['ng'])
652656
});
653657
animationReflowQueue = [];
654658
animationTimer = null;
659+
lookupCache = {};
655660
}, 10, false);
656661
}
657662

658-
function animate(element, className, done) {
659-
if(['ng-enter','ng-leave','ng-move'].indexOf(className) == -1) {
660-
var existingDuration = 0;
663+
function getElementAnimationDetails(element, cacheKey, onlyCheckTransition) {
664+
var data = lookupCache[cacheKey];
665+
if(!data) {
666+
var transitionDuration = 0, transitionDelay = 0,
667+
animationDuration = 0, animationDelay = 0;
668+
669+
//we want all the styles defined before and after
661670
forEach(element, function(element) {
662671
if (element.nodeType == ELEMENT_NODE) {
663672
var elementStyles = $window.getComputedStyle(element) || {};
664-
existingDuration = Math.max(parseMaxTime(elementStyles[transitionProp + durationKey]),
665-
existingDuration);
673+
674+
transitionDuration = Math.max(parseMaxTime(elementStyles[transitionProp + durationKey]), transitionDuration);
675+
676+
if(!onlyCheckTransition) {
677+
transitionDelay = Math.max(parseMaxTime(elementStyles[transitionProp + delayKey]), transitionDelay);
678+
679+
animationDelay = Math.max(parseMaxTime(elementStyles[animationProp + delayKey]), animationDelay);
680+
681+
var aDuration = parseMaxTime(elementStyles[animationProp + durationKey]);
682+
683+
if(aDuration > 0) {
684+
aDuration *= parseInt(elementStyles[animationProp + animationIterationCountKey]) || 1;
685+
}
686+
687+
animationDuration = Math.max(aDuration, animationDuration);
688+
}
666689
}
667690
});
668-
if(existingDuration > 0) {
669-
done();
670-
return;
671-
}
691+
data = {
692+
transitionDelay : transitionDelay,
693+
animationDelay : animationDelay,
694+
transitionDuration : transitionDuration,
695+
animationDuration : animationDuration
696+
};
697+
lookupCache[cacheKey] = data;
672698
}
699+
return data;
700+
}
673701

674-
element.addClass(className);
675-
676-
//we want all the styles defined before and after
677-
var transitionDuration = 0,
678-
animationDuration = 0,
679-
transitionDelay = 0,
680-
animationDelay = 0;
681-
forEach(element, function(element) {
682-
if (element.nodeType == ELEMENT_NODE) {
683-
var elementStyles = $window.getComputedStyle(element) || {};
702+
function parseMaxTime(str) {
703+
var total = 0, values = angular.isString(str) ? str.split(/\s*,\s*/) : [];
704+
forEach(values, function(value) {
705+
total = Math.max(parseFloat(value) || 0, total);
706+
});
707+
return total;
708+
}
684709

685-
transitionDelay = Math.max(parseMaxTime(elementStyles[transitionProp + delayKey]), transitionDelay);
710+
function getCacheKey(element) {
711+
var parent = element.parent();
712+
var parentID = parent.data(NG_ANIMATE_PARENT_KEY);
713+
if(!parentID) {
714+
parent.data(NG_ANIMATE_PARENT_KEY, ++parentCounter);
715+
parentID = parentCounter;
716+
}
717+
return parentID + '-' + element[0].className;
718+
}
686719

687-
animationDelay = Math.max(parseMaxTime(elementStyles[animationProp + delayKey]), animationDelay);
720+
function animate(element, className, done) {
688721

689-
transitionDuration = Math.max(parseMaxTime(elementStyles[transitionProp + durationKey]), transitionDuration);
722+
var cacheKey = getCacheKey(element);
723+
if(getElementAnimationDetails(element, cacheKey, true).transitionDuration > 0) {
690724

691-
var aDuration = parseMaxTime(elementStyles[animationProp + durationKey]);
725+
done();
726+
return;
727+
}
692728

693-
if(aDuration > 0) {
694-
aDuration *= parseInt(elementStyles[animationProp + animationIterationCountKey]) || 1;
695-
}
729+
element.addClass(className);
696730

697-
animationDuration = Math.max(aDuration, animationDuration);
698-
}
699-
});
731+
var timings = getElementAnimationDetails(element, cacheKey + ' ' + className);
700732

701733
/* there is no point in performing a reflow if the animation
702734
timeout is empty (this would cause a flicker bug normally
703735
in the page. There is also no point in performing an animation
704736
that only has a delay and no duration */
705-
var maxDuration = Math.max(transitionDuration, animationDuration);
737+
var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
706738
if(maxDuration > 0) {
707-
var maxDelayTime = Math.max(transitionDelay, animationDelay) * 1000,
739+
var maxDelayTime = Math.max(timings.transitionDelay, timings.animationDelay) * 1000,
708740
startTime = Date.now(),
709741
node = element[0];
710742

711743
//temporarily disable the transition so that the enter styles
712744
//don't animate twice (this is here to avoid a bug in Chrome/FF).
713-
if(transitionDuration > 0) {
745+
if(timings.transitionDuration > 0) {
714746
node.style[transitionProp + propertyKey] = 'none';
715747
}
716748

@@ -723,7 +755,7 @@ angular.module('ngAnimate', ['ng'])
723755
var css3AnimationEvents = animationendEvent + ' ' + transitionendEvent;
724756

725757
afterReflow(function() {
726-
if(transitionDuration > 0) {
758+
if(timings.transitionDuration > 0) {
727759
node.style[transitionProp + propertyKey] = '';
728760
}
729761
element.addClass(activeClassName);
@@ -768,13 +800,6 @@ angular.module('ngAnimate', ['ng'])
768800
}
769801
}
770802

771-
function parseMaxTime(str) {
772-
var total = 0, values = angular.isString(str) ? str.split(/\s*,\s*/) : [];
773-
forEach(values, function(value) {
774-
total = Math.max(parseFloat(value) || 0, total);
775-
});
776-
return total;
777-
}
778803
}
779804

780805
return {

test/ngAnimate/animateSpec.js

+49-4
Original file line numberDiff line numberDiff line change
@@ -726,10 +726,10 @@ describe("ngAnimate", function() {
726726
it('should re-evaluate the CSS classes for an animation each time',
727727
inject(function($animate, $rootScope, $sniffer, $rootElement, $timeout, $compile) {
728728

729-
ss.addRule('.abc', '-webkit-transition:22s linear all;' +
730-
'transition:22s linear all;');
731-
ss.addRule('.xyz', '-webkit-transition:11s linear all;' +
732-
'transition:11s linear all;');
729+
ss.addRule('.abc.ng-enter', '-webkit-transition:22s linear all;' +
730+
'transition:22s linear all;');
731+
ss.addRule('.xyz.ng-enter', '-webkit-transition:11s linear all;' +
732+
'transition:11s linear all;');
733733

734734
var parent = $compile('<div><span ng-class="klass"></span></div>')($rootScope);
735735
var element = parent.find('span');
@@ -1875,4 +1875,49 @@ describe("ngAnimate", function() {
18751875
expect(intercepted).toBe(true);
18761876
});
18771877
});
1878+
1879+
it("should cache the response from getComputedStyle if each successive element has the same className value and parent until the first reflow hits", function() {
1880+
var count = 0;
1881+
module(function($provide) {
1882+
$provide.value('$window', {
1883+
document : jqLite(window.document),
1884+
getComputedStyle: function(element) {
1885+
count++;
1886+
return window.getComputedStyle(element);
1887+
}
1888+
});
1889+
});
1890+
1891+
inject(function($animate, $rootScope, $compile, $rootElement, $timeout, $document, $sniffer) {
1892+
if(!$sniffer.transitions) return;
1893+
1894+
$animate.enabled(true);
1895+
1896+
var element = $compile('<div></div>')($rootScope);
1897+
$rootElement.append(element);
1898+
jqLite($document[0].body).append($rootElement);
1899+
1900+
for(var i=0;i<20;i++) {
1901+
var kid = $compile('<div class="kid"></div>')($rootScope);
1902+
$animate.enter(kid, element);
1903+
}
1904+
$rootScope.$digest();
1905+
$timeout.flush();
1906+
1907+
expect(count).toBe(2);
1908+
1909+
dealoc(element);
1910+
count = 0;
1911+
1912+
for(var i=0;i<20;i++) {
1913+
var kid = $compile('<div class="kid c-'+i+'"></div>')($rootScope);
1914+
$animate.enter(kid, element);
1915+
}
1916+
1917+
$rootScope.$digest();
1918+
$timeout.flush();
1919+
1920+
expect(count).toBe(40);
1921+
});
1922+
});
18781923
});

0 commit comments

Comments
 (0)