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

Commit e58c06b

Browse files
committed
perf(ngAnimate): cache repeated calls to addClass/removeClass
Closes #14165
1 parent f1aea54 commit e58c06b

File tree

9 files changed

+398
-85
lines changed

9 files changed

+398
-85
lines changed

angularFiles.js

+1
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ var angularFiles = {
101101
'src/ngAnimate/animateJs.js',
102102
'src/ngAnimate/animateJsDriver.js',
103103
'src/ngAnimate/animateQueue.js',
104+
'src/ngAnimate/animateCache.js',
104105
'src/ngAnimate/animation.js',
105106
'src/ngAnimate/ngAnimateSwap.js',
106107
'src/ngAnimate/module.js'

src/ngAnimate/animateCache.js

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
'use strict';
2+
3+
var $$AnimateCacheProvider = function() {
4+
var KEY = "$$ngAnimateParentKey";
5+
var parentCounter = 0;
6+
var cache = Object.create(null);
7+
8+
this.$get = [function() {
9+
return {
10+
cacheKey: function(node, method, addClass, removeClass) {
11+
var parentNode = node.parentNode;
12+
var parentID = parentNode[KEY] || (parentNode[KEY] = ++parentCounter);
13+
var parts = [parentID, method, node.getAttribute('class')];
14+
if (addClass) {
15+
parts.push(addClass);
16+
}
17+
if (removeClass) {
18+
parts.push(removeClass);
19+
}
20+
return parts.join('-');
21+
},
22+
23+
containsCachedValidAnimation: function(key) {
24+
var entry = cache[key];
25+
26+
// nothing cached, so go ahead and animate
27+
// otherwise it should be a valid animation
28+
return (!entry || entry.isValid) ? true : false;
29+
},
30+
31+
flush: function() {
32+
cache = Object.create(null);
33+
},
34+
35+
count: function(key) {
36+
var entry = cache[key];
37+
return entry ? entry.total : 0;
38+
},
39+
40+
get: function(key) {
41+
var entry = cache[key];
42+
return entry && entry.value;
43+
},
44+
45+
put: function(key, value, isValid) {
46+
if (!cache[key]) {
47+
cache[key] = { total: 1, value: value, isValid: isValid };
48+
} else {
49+
cache[key].total++;
50+
cache[key].value = value;
51+
}
52+
}
53+
};
54+
}];
55+
};

src/ngAnimate/animateCss.js

+34-59
Original file line numberDiff line numberDiff line change
@@ -303,33 +303,6 @@ function getCssTransitionDurationStyle(duration, applyOnlyDuration) {
303303
return [style, value];
304304
}
305305

306-
function createLocalCacheLookup() {
307-
var cache = Object.create(null);
308-
return {
309-
flush: function() {
310-
cache = Object.create(null);
311-
},
312-
313-
count: function(key) {
314-
var entry = cache[key];
315-
return entry ? entry.total : 0;
316-
},
317-
318-
get: function(key) {
319-
var entry = cache[key];
320-
return entry && entry.value;
321-
},
322-
323-
put: function(key, value) {
324-
if (!cache[key]) {
325-
cache[key] = { total: 1, value: value };
326-
} else {
327-
cache[key].total++;
328-
}
329-
}
330-
};
331-
}
332-
333306
// we do not reassign an already present style value since
334307
// if we detect the style property value again we may be
335308
// detecting styles that were added via the `from` styles.
@@ -348,26 +321,15 @@ function registerRestorableStyles(backup, node, properties) {
348321
}
349322

350323
var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
351-
var gcsLookup = createLocalCacheLookup();
352-
var gcsStaggerLookup = createLocalCacheLookup();
353-
354-
this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout',
324+
this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout', '$$animateCache',
355325
'$$forceReflow', '$sniffer', '$$rAFScheduler', '$$animateQueue',
356-
function($window, $$jqLite, $$AnimateRunner, $timeout,
326+
function($window, $$jqLite, $$AnimateRunner, $timeout, $$animateCache,
357327
$$forceReflow, $sniffer, $$rAFScheduler, $$animateQueue) {
358328

359329
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
360330

361-
var parentCounter = 0;
362-
function gcsHashFn(node, extraClasses) {
363-
var KEY = "$$ngAnimateParentKey";
364-
var parentNode = node.parentNode;
365-
var parentID = parentNode[KEY] || (parentNode[KEY] = ++parentCounter);
366-
return parentID + '-' + node.getAttribute('class') + '-' + extraClasses;
367-
}
368-
369-
function computeCachedCssStyles(node, className, cacheKey, properties) {
370-
var timings = gcsLookup.get(cacheKey);
331+
function computeCachedCssStyles(node, className, cacheKey, allowInvalid, properties) {
332+
var timings = $$animateCache.get(cacheKey);
371333

372334
if (!timings) {
373335
timings = computeCssStyles($window, node, properties);
@@ -376,20 +338,26 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
376338
}
377339
}
378340

341+
// if an GCS call doesn't return anything valid for the animation then we
342+
// should mark that so that repeated classAdd/removeRemove calls are skipped
343+
var isValid = allowInvalid || (timings.transitionDuration > 0 || timings.animationDuration > 0);
344+
379345
// we keep putting this in multiple times even though the value and the cacheKey are the same
380346
// because we're keeping an internal tally of how many duplicate animations are detected.
381-
gcsLookup.put(cacheKey, timings);
347+
$$animateCache.put(cacheKey, timings, isValid);
348+
382349
return timings;
383350
}
384351

385352
function computeCachedCssStaggerStyles(node, className, cacheKey, properties) {
386353
var stagger;
354+
var staggerCacheKey = 'stagger-' + cacheKey;
387355

388356
// if we have one or more existing matches of matching elements
389357
// containing the same parent + CSS styles (which is how cacheKey works)
390358
// then staggering is possible
391-
if (gcsLookup.count(cacheKey) > 0) {
392-
stagger = gcsStaggerLookup.get(cacheKey);
359+
if ($$animateCache.count(cacheKey) > 0) {
360+
stagger = $$animateCache.get(staggerCacheKey);
393361

394362
if (!stagger) {
395363
var staggerClassName = pendClasses(className, '-stagger');
@@ -404,7 +372,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
404372

405373
$$jqLite.removeClass(node, staggerClassName);
406374

407-
gcsStaggerLookup.put(cacheKey, stagger);
375+
$$animateCache.put(staggerCacheKey, stagger, true);
408376
}
409377
}
410378

@@ -416,8 +384,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
416384
function waitUntilQuiet(callback) {
417385
rafWaitQueue.push(callback);
418386
$$rAFScheduler.waitUntilQuiet(function() {
419-
gcsLookup.flush();
420-
gcsStaggerLookup.flush();
387+
$$animateCache.flush();
421388

422389
// DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable.
423390
// PLEASE EXAMINE THE `$$forceReflow` service to understand why.
@@ -432,8 +399,8 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
432399
});
433400
}
434401

435-
function computeTimings(node, className, cacheKey) {
436-
var timings = computeCachedCssStyles(node, className, cacheKey, DETECT_CSS_PROPERTIES);
402+
function computeTimings(node, className, cacheKey, allowInvalid) {
403+
var timings = computeCachedCssStyles(node, className, cacheKey, allowInvalid, DETECT_CSS_PROPERTIES);
437404
var aD = timings.animationDelay;
438405
var tD = timings.transitionDelay;
439406
timings.maxDelay = aD && tD
@@ -520,7 +487,6 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
520487

521488
var preparationClasses = [structuralClassName, addRemoveClassName].join(' ').trim();
522489
var fullClassName = classes + ' ' + preparationClasses;
523-
var activeClasses = pendClasses(preparationClasses, ACTIVE_CLASS_SUFFIX);
524490
var hasToStyles = styles.to && Object.keys(styles.to).length > 0;
525491
var containsKeyframeAnimation = (options.keyframeStyle || '').length > 0;
526492

@@ -533,7 +499,12 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
533499
return closeAndReturnNoopAnimator();
534500
}
535501

536-
var cacheKey, stagger;
502+
var stagger, cacheKey = $$animateCache.cacheKey(node, method, options.addClass, options.removeClass);
503+
if (!$$animateCache.containsCachedValidAnimation(cacheKey)) {
504+
preparationClasses = null;
505+
return closeAndReturnNoopAnimator();
506+
}
507+
537508
if (options.stagger > 0) {
538509
var staggerVal = parseFloat(options.stagger);
539510
stagger = {
@@ -543,7 +514,6 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
543514
animationDuration: 0
544515
};
545516
} else {
546-
cacheKey = gcsHashFn(node, fullClassName);
547517
stagger = computeCachedCssStaggerStyles(node, preparationClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES);
548518
}
549519

@@ -577,7 +547,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
577547
var itemIndex = stagger
578548
? options.staggerIndex >= 0
579549
? options.staggerIndex
580-
: gcsLookup.count(cacheKey)
550+
: $$animateCache.count(cacheKey)
581551
: 0;
582552

583553
var isFirst = itemIndex === 0;
@@ -592,7 +562,7 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
592562
blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE);
593563
}
594564

595-
var timings = computeTimings(node, fullClassName, cacheKey);
565+
var timings = computeTimings(node, fullClassName, cacheKey, !isStructural);
596566
var relativeDelay = timings.maxDelay;
597567
maxDelay = Math.max(relativeDelay, 0);
598568
maxDuration = timings.maxDuration;
@@ -630,6 +600,8 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
630600
return closeAndReturnNoopAnimator();
631601
}
632602

603+
var activeClasses = pendClasses(preparationClasses, ACTIVE_CLASS_SUFFIX);
604+
633605
if (options.delay != null) {
634606
var delayStyle;
635607
if (typeof options.delay !== "boolean") {
@@ -717,10 +689,13 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
717689
animationClosed = true;
718690
animationPaused = false;
719691

720-
if (!options.$$skipPreparationClasses) {
692+
if (preparationClasses && !options.$$skipPreparationClasses) {
721693
$$jqLite.removeClass(element, preparationClasses);
722694
}
723-
$$jqLite.removeClass(element, activeClasses);
695+
696+
if (activeClasses) {
697+
$$jqLite.removeClass(element, activeClasses);
698+
}
724699

725700
blockKeyframeAnimations(node, false);
726701
blockTransitions(node, false);
@@ -893,9 +868,9 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
893868

894869
if (flags.recalculateTimingStyles) {
895870
fullClassName = node.className + ' ' + preparationClasses;
896-
cacheKey = gcsHashFn(node, fullClassName);
871+
cacheKey = $$animateCache.cacheKey(node, method, options.addClass, options.removeClass);
897872

898-
timings = computeTimings(node, fullClassName, cacheKey);
873+
timings = computeTimings(node, fullClassName, cacheKey, false);
899874
relativeDelay = timings.maxDelay;
900875
maxDelay = Math.max(relativeDelay, 0);
901876
maxDuration = timings.maxDuration;

0 commit comments

Comments
 (0)