refactor(ngAnimate): simplify functions and remove redundant args/calls

Simplifies/Optimizes the following functions:

- `areAnimationsAllowed()`
- `cleanupEventListeners()`
- `closeChildAnimations()`
- `clearElementAnimationState()`
- `markElementAnimationState()`
- `findCallbacks()`

Although not its primary aim, this commit also offers a small performance boost
to animations (~5% as measured with the `animation-bp` benchmark).
This commit is contained in:
Georgios Kalpakas
2016-12-15 15:04:50 +02:00
parent 565ca6a1f5
commit e7d8eee46d
+60 -73
View File
@@ -36,9 +36,9 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
}
}
function isAllowed(ruleType, element, currentAnimation, previousAnimation) {
function isAllowed(ruleType, currentAnimation, previousAnimation) {
return rules[ruleType].some(function(fn) {
return fn(element, currentAnimation, previousAnimation);
return fn(currentAnimation, previousAnimation);
});
}
@@ -48,40 +48,40 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
return and ? a && b : a || b;
}
rules.join.push(function(element, newAnimation, currentAnimation) {
rules.join.push(function(newAnimation, currentAnimation) {
// if the new animation is class-based then we can just tack that on
return !newAnimation.structural && hasAnimationClasses(newAnimation);
});
rules.skip.push(function(element, newAnimation, currentAnimation) {
rules.skip.push(function(newAnimation, currentAnimation) {
// there is no need to animate anything if no classes are being added and
// there is no structural animation that will be triggered
return !newAnimation.structural && !hasAnimationClasses(newAnimation);
});
rules.skip.push(function(element, newAnimation, currentAnimation) {
rules.skip.push(function(newAnimation, currentAnimation) {
// why should we trigger a new structural animation if the element will
// be removed from the DOM anyway?
return currentAnimation.event === 'leave' && newAnimation.structural;
});
rules.skip.push(function(element, newAnimation, currentAnimation) {
rules.skip.push(function(newAnimation, currentAnimation) {
// if there is an ongoing current animation then don't even bother running the class-based animation
return currentAnimation.structural && currentAnimation.state === RUNNING_STATE && !newAnimation.structural;
});
rules.cancel.push(function(element, newAnimation, currentAnimation) {
rules.cancel.push(function(newAnimation, currentAnimation) {
// there can never be two structural animations running at the same time
return currentAnimation.structural && newAnimation.structural;
});
rules.cancel.push(function(element, newAnimation, currentAnimation) {
rules.cancel.push(function(newAnimation, currentAnimation) {
// if the previous animation is already running, but the new animation will
// be triggered, but the new animation is structural
return currentAnimation.state === RUNNING_STATE && newAnimation.structural;
});
rules.cancel.push(function(element, newAnimation, currentAnimation) {
rules.cancel.push(function(newAnimation, currentAnimation) {
// cancel the animation if classes added / removed in both animation cancel each other out,
// but only if the current animation isn't structural
@@ -181,10 +181,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
return this === arg || !!(this.compareDocumentPosition(arg) & 16);
};
function findCallbacks(parent, element, event) {
var targetNode = getDomNode(element);
var targetParentNode = getDomNode(parent);
function findCallbacks(targetParentNode, targetNode, event) {
var matches = [];
var entries = callbackRegistry[event];
if (entries) {
@@ -209,11 +206,11 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
});
}
function cleanupEventListeners(phase, element) {
if (phase === 'close' && !element[0].parentNode) {
function cleanupEventListeners(phase, node) {
if (phase === 'close' && !node.parentNode) {
// If the element is not attached to a parentNode, it has been removed by
// the domOperation, and we can safely remove the event callbacks
$animate.off(element);
$animate.off(node);
}
}
@@ -311,12 +308,9 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
// the input data when running `$animateCss`.
var options = copy(initialOptions);
var node, parent;
element = stripCommentsFromElement(element);
if (element) {
node = getDomNode(element);
parent = element.parent();
}
var node = getDomNode(element);
var parentNode = node && node.parentNode;
options = prepareAnimationOptions(options);
@@ -381,7 +375,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
// there is no point in traversing the same collection of parent ancestors if a followup
// animation will be run on the same element that already did all that checking work
if (!skipAnimations && (!hasExistingAnimation || existingAnimation.state !== PRE_DIGEST_STATE)) {
skipAnimations = !areAnimationsAllowed(element, parent, event);
skipAnimations = !areAnimationsAllowed(node, parentNode, event);
}
if (skipAnimations) {
@@ -393,7 +387,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
}
if (isStructural) {
closeChildAnimations(element);
closeChildAnimations(node);
}
var newAnimation = {
@@ -408,7 +402,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
};
if (hasExistingAnimation) {
var skipAnimationFlag = isAllowed('skip', element, newAnimation, existingAnimation);
var skipAnimationFlag = isAllowed('skip', newAnimation, existingAnimation);
if (skipAnimationFlag) {
if (existingAnimation.state === RUNNING_STATE) {
close();
@@ -418,7 +412,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
return existingAnimation.runner;
}
}
var cancelAnimationFlag = isAllowed('cancel', element, newAnimation, existingAnimation);
var cancelAnimationFlag = isAllowed('cancel', newAnimation, existingAnimation);
if (cancelAnimationFlag) {
if (existingAnimation.state === RUNNING_STATE) {
// this will end the animation right away and it is safe
@@ -440,7 +434,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
// a joined animation means that this animation will take over the existing one
// so an example would involve a leave animation taking over an enter. Then when
// the postDigest kicks in the enter will be ignored.
var joinAnimationFlag = isAllowed('join', element, newAnimation, existingAnimation);
var joinAnimationFlag = isAllowed('join', newAnimation, existingAnimation);
if (joinAnimationFlag) {
if (existingAnimation.state === RUNNING_STATE) {
normalizeAnimationDetails(element, newAnimation);
@@ -474,7 +468,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
if (!isValidAnimation) {
close();
clearElementAnimationState(element);
clearElementAnimationState(node);
return runner;
}
@@ -482,7 +476,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
var counter = (existingAnimation.counter || 0) + 1;
newAnimation.counter = counter;
markElementAnimationState(element, PRE_DIGEST_STATE, newAnimation);
markElementAnimationState(node, PRE_DIGEST_STATE, newAnimation);
$rootScope.$$postDigest(function() {
var animationDetails = activeAnimationsLookup.get(node);
@@ -523,7 +517,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
// isn't allowed to animate from here then we need to clear the state of the element
// so that any future animations won't read the expired animation data.
if (!isValidAnimation) {
clearElementAnimationState(element);
clearElementAnimationState(node);
}
return;
@@ -535,7 +529,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
? 'setClass'
: animationDetails.event;
markElementAnimationState(element, RUNNING_STATE);
markElementAnimationState(node, RUNNING_STATE);
var realRunner = $$animation(element, event, animationDetails.options);
// this will update the runner's flow-control events based on
@@ -547,7 +541,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
close(!status);
var animationDetails = activeAnimationsLookup.get(node);
if (animationDetails && animationDetails.counter === counter) {
clearElementAnimationState(getDomNode(element));
clearElementAnimationState(node);
}
notifyProgress(runner, event, 'close', {});
});
@@ -557,7 +551,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
function notifyProgress(runner, event, phase, data) {
runInNextPostDigestOrNow(function() {
var callbacks = findCallbacks(parent, element, event);
var callbacks = findCallbacks(parentNode, node, event);
if (callbacks.length) {
// do not optimize this call here to RAF because
// we don't know how heavy the callback code here will
@@ -567,10 +561,10 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
forEach(callbacks, function(callback) {
callback(element, phase, data);
});
cleanupEventListeners(phase, element);
cleanupEventListeners(phase, node);
});
} else {
cleanupEventListeners(phase, element);
cleanupEventListeners(phase, node);
}
});
runner.progress(event, phase, data);
@@ -585,8 +579,7 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
}
}
function closeChildAnimations(element) {
var node = getDomNode(element);
function closeChildAnimations(node) {
var children = node.querySelectorAll('[' + NG_ANIMATE_ATTR_NAME + ']');
forEach(children, function(child) {
var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME), 10);
@@ -604,16 +597,11 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
});
}
function clearElementAnimationState(element) {
var node = getDomNode(element);
function clearElementAnimationState(node) {
node.removeAttribute(NG_ANIMATE_ATTR_NAME);
activeAnimationsLookup.remove(node);
}
function isMatchingElement(nodeOrElmA, nodeOrElmB) {
return getDomNode(nodeOrElmA) === getDomNode(nodeOrElmB);
}
/**
* This fn returns false if any of the following is true:
* a) animations on any parent element are disabled, and animations on the element aren't explicitly allowed
@@ -621,54 +609,54 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
* c) the element is not a child of the body
* d) the element is not a child of the $rootElement
*/
function areAnimationsAllowed(element, parentElement, event) {
var bodyElement = jqLite($document[0].body);
var bodyElementDetected = isMatchingElement(element, bodyElement) || element[0].nodeName === 'HTML';
var rootElementDetected = isMatchingElement(element, $rootElement);
var parentAnimationDetected = false;
var animateChildren;
var elementDisabled = disabledElementsLookup.get(getDomNode(element));
function areAnimationsAllowed(node, parentNode, event) {
var bodyNode = $document[0].body;
var rootNode = getDomNode($rootElement);
var parentHost = jqLite.data(element[0], NG_ANIMATE_PIN_DATA);
var bodyNodeDetected = (node === bodyNode) || node.nodeName === 'HTML';
var rootNodeDetected = (node === rootNode);
var parentAnimationDetected = false;
var elementDisabled = disabledElementsLookup.get(node);
var animateChildren;
var parentHost = jqLite.data(node, NG_ANIMATE_PIN_DATA);
if (parentHost) {
parentElement = parentHost;
parentNode = getDomNode(parentHost);
}
parentElement = getDomNode(parentElement);
while (parentElement) {
if (!rootElementDetected) {
while (parentNode) {
if (!rootNodeDetected) {
// angular doesn't want to attempt to animate elements outside of the application
// therefore we need to ensure that the rootElement is an ancestor of the current element
rootElementDetected = isMatchingElement(parentElement, $rootElement);
rootNodeDetected = (parentNode === rootNode);
}
if (parentElement.nodeType !== ELEMENT_NODE) {
if (parentNode.nodeType !== ELEMENT_NODE) {
// no point in inspecting the #document element
break;
}
var details = activeAnimationsLookup.get(parentElement) || {};
var details = activeAnimationsLookup.get(parentNode) || {};
// either an enter, leave or move animation will commence
// therefore we can't allow any animations to take place
// but if a parent animation is class-based then that's ok
if (!parentAnimationDetected) {
var parentElementDisabled = disabledElementsLookup.get(parentElement);
var parentNodeDisabled = disabledElementsLookup.get(parentNode);
if (parentElementDisabled === true && elementDisabled !== false) {
if (parentNodeDisabled === true && elementDisabled !== false) {
// disable animations if the user hasn't explicitly enabled animations on the
// current element
elementDisabled = true;
// element is disabled via parent element, no need to check anything else
break;
} else if (parentElementDisabled === false) {
} else if (parentNodeDisabled === false) {
elementDisabled = false;
}
parentAnimationDetected = details.structural;
}
if (isUndefined(animateChildren) || animateChildren === true) {
var value = jqLite.data(parentElement, NG_ANIMATE_CHILDREN_DATA);
var value = jqLite.data(parentNode, NG_ANIMATE_CHILDREN_DATA);
if (isDefined(value)) {
animateChildren = value;
}
@@ -677,40 +665,39 @@ var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animate
// there is no need to continue traversing at this point
if (parentAnimationDetected && animateChildren === false) break;
if (!bodyElementDetected) {
if (!bodyNodeDetected) {
// we also need to ensure that the element is or will be a part of the body element
// otherwise it is pointless to even issue an animation to be rendered
bodyElementDetected = isMatchingElement(parentElement, bodyElement);
bodyNodeDetected = (parentNode === bodyNode);
}
if (bodyElementDetected && rootElementDetected) {
if (bodyNodeDetected && rootNodeDetected) {
// If both body and root have been found, any other checks are pointless,
// as no animation data should live outside the application
break;
}
if (!rootElementDetected) {
// If no rootElement is detected, check if the parentElement is pinned to another element
parentHost = jqLite.data(parentElement, NG_ANIMATE_PIN_DATA);
if (!rootNodeDetected) {
// If `rootNode` is not detected, check if `parentNode` is pinned to another element
parentHost = jqLite.data(parentNode, NG_ANIMATE_PIN_DATA);
if (parentHost) {
// The pin target element becomes the next parent element
parentElement = getDomNode(parentHost);
parentNode = getDomNode(parentHost);
continue;
}
}
parentElement = parentElement.parentNode;
parentNode = parentNode.parentNode;
}
var allowAnimation = (!parentAnimationDetected || animateChildren) && elementDisabled !== true;
return allowAnimation && rootElementDetected && bodyElementDetected;
return allowAnimation && rootNodeDetected && bodyNodeDetected;
}
function markElementAnimationState(element, state, details) {
function markElementAnimationState(node, state, details) {
details = details || {};
details.state = state;
var node = getDomNode(element);
node.setAttribute(NG_ANIMATE_ATTR_NAME, state);
var oldValue = activeAnimationsLookup.get(node);