perf(animations): do not create a closure each time a node is removed
This commit is contained in:
parent
d837bfc2d7
commit
fe6b39d585
@ -13,13 +13,24 @@ import {AnimationTransitionInstruction} from '../dsl/animation_transition_instru
|
|||||||
import {AnimationTrigger} from '../dsl/animation_trigger';
|
import {AnimationTrigger} from '../dsl/animation_trigger';
|
||||||
import {ElementInstructionMap} from '../dsl/element_instruction_map';
|
import {ElementInstructionMap} from '../dsl/element_instruction_map';
|
||||||
import {AnimationStyleNormalizer} from '../dsl/style_normalization/animation_style_normalizer';
|
import {AnimationStyleNormalizer} from '../dsl/style_normalization/animation_style_normalizer';
|
||||||
import {ENTER_CLASSNAME, LEAVE_CLASSNAME, LEAVE_SELECTOR, NG_ANIMATING_CLASSNAME, NG_TRIGGER_CLASSNAME, NG_TRIGGER_SELECTOR, copyObj, eraseStyles, setStyles} from '../util';
|
import {ENTER_CLASSNAME, LEAVE_CLASSNAME, NG_ANIMATING_CLASSNAME, NG_TRIGGER_CLASSNAME, NG_TRIGGER_SELECTOR, copyObj, eraseStyles, setStyles} from '../util';
|
||||||
|
|
||||||
import {AnimationDriver} from './animation_driver';
|
import {AnimationDriver} from './animation_driver';
|
||||||
import {getOrSetAsInMap, listenOnPlayer, makeAnimationEvent, normalizeKeyframes, optimizeGroupPlayer} from './shared';
|
import {getOrSetAsInMap, listenOnPlayer, makeAnimationEvent, normalizeKeyframes, optimizeGroupPlayer} from './shared';
|
||||||
|
|
||||||
const EMPTY_PLAYER_ARRAY: AnimationPlayer[] = [];
|
const EMPTY_PLAYER_ARRAY: AnimationPlayer[] = [];
|
||||||
const NOOP_FN = () => {};
|
const NULL_REMOVAL_STATE: ElementAnimationState = {
|
||||||
|
namespaceId: '',
|
||||||
|
setForRemoval: null,
|
||||||
|
hasAnimation: false,
|
||||||
|
removedBeforeQueried: false
|
||||||
|
};
|
||||||
|
const NULL_REMOVED_QUERIED_STATE: ElementAnimationState = {
|
||||||
|
namespaceId: '',
|
||||||
|
setForRemoval: null,
|
||||||
|
hasAnimation: false,
|
||||||
|
removedBeforeQueried: true
|
||||||
|
};
|
||||||
|
|
||||||
interface TriggerListener {
|
interface TriggerListener {
|
||||||
name: string;
|
name: string;
|
||||||
@ -37,6 +48,15 @@ export interface QueueInstruction {
|
|||||||
isFallbackTransition: boolean;
|
isFallbackTransition: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const REMOVAL_FLAG = '__ng_removed';
|
||||||
|
|
||||||
|
export interface ElementAnimationState {
|
||||||
|
setForRemoval: any;
|
||||||
|
hasAnimation: boolean;
|
||||||
|
namespaceId: string;
|
||||||
|
removedBeforeQueried: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export class StateValue {
|
export class StateValue {
|
||||||
public value: string;
|
public value: string;
|
||||||
public options: AnimationOptions;
|
public options: AnimationOptions;
|
||||||
@ -245,7 +265,7 @@ export class AnimationTransitionNamespace {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onElementDestroy(element: any) {
|
clearElementCache(element: any) {
|
||||||
this._engine.statesByElement.delete(element);
|
this._engine.statesByElement.delete(element);
|
||||||
this._elementListeners.delete(element);
|
this._elementListeners.delete(element);
|
||||||
const elementPlayers = this._engine.playersByElement.get(element);
|
const elementPlayers = this._engine.playersByElement.get(element);
|
||||||
@ -267,14 +287,13 @@ export class AnimationTransitionNamespace {
|
|||||||
|
|
||||||
this.removeNode(elm, context, true);
|
this.removeNode(elm, context, true);
|
||||||
} else {
|
} else {
|
||||||
this._onElementDestroy(elm);
|
this.clearElementCache(elm);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
removeNode(element: any, context: any, doNotRecurse?: boolean): void {
|
removeNode(element: any, context: any, doNotRecurse?: boolean): void {
|
||||||
const engine = this._engine;
|
const engine = this._engine;
|
||||||
engine.markElementAsRemoved(element);
|
|
||||||
|
|
||||||
if (!doNotRecurse && element.childElementCount) {
|
if (!doNotRecurse && element.childElementCount) {
|
||||||
this._destroyInnerNodes(element, context, true);
|
this._destroyInnerNodes(element, context, true);
|
||||||
@ -295,12 +314,8 @@ export class AnimationTransitionNamespace {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (players.length) {
|
if (players.length) {
|
||||||
optimizeGroupPlayer(players).onDone(() => {
|
engine.markElementAsRemoved(this.id, element, true, context);
|
||||||
engine.destroyInnerAnimations(element);
|
optimizeGroupPlayer(players).onDone(() => engine.processLeaveNode(element));
|
||||||
this._onElementDestroy(element);
|
|
||||||
engine._onRemovalComplete(element, context);
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -365,15 +380,11 @@ export class AnimationTransitionNamespace {
|
|||||||
// whether or not a parent has an animation we need to delay the deferral of the leave
|
// whether or not a parent has an animation we need to delay the deferral of the leave
|
||||||
// operation until we have more information (which we do after flush() has been called)
|
// operation until we have more information (which we do after flush() has been called)
|
||||||
if (containsPotentialParentTransition) {
|
if (containsPotentialParentTransition) {
|
||||||
engine.queuedRemovals.set(element, () => {
|
engine.markElementAsRemoved(this.id, element, false, context);
|
||||||
engine.destroyInnerAnimations(element);
|
|
||||||
this._onElementDestroy(element);
|
|
||||||
engine._onRemovalComplete(element, context);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// we do this after the flush has occurred such
|
// we do this after the flush has occurred such
|
||||||
// that the callbacks can be fired
|
// that the callbacks can be fired
|
||||||
engine.afterFlush(() => this._onElementDestroy(element));
|
engine.afterFlush(() => this.clearElementCache(element));
|
||||||
engine.destroyInnerAnimations(element);
|
engine.destroyInnerAnimations(element);
|
||||||
engine._onRemovalComplete(element, context);
|
engine._onRemovalComplete(element, context);
|
||||||
}
|
}
|
||||||
@ -447,7 +458,6 @@ export interface QueuedTransition {
|
|||||||
|
|
||||||
export class TransitionAnimationEngine {
|
export class TransitionAnimationEngine {
|
||||||
public players: TransitionAnimationPlayer[] = [];
|
public players: TransitionAnimationPlayer[] = [];
|
||||||
public queuedRemovals = new Map<any, () => any>();
|
|
||||||
public newHostElements = new Map<any, AnimationTransitionNamespace>();
|
public newHostElements = new Map<any, AnimationTransitionNamespace>();
|
||||||
public playersByElement = new Map<any, TransitionAnimationPlayer[]>();
|
public playersByElement = new Map<any, TransitionAnimationPlayer[]>();
|
||||||
public playersByQueriedElement = new Map<any, TransitionAnimationPlayer[]>();
|
public playersByQueriedElement = new Map<any, TransitionAnimationPlayer[]>();
|
||||||
@ -462,6 +472,7 @@ export class TransitionAnimationEngine {
|
|||||||
|
|
||||||
public namespacesByHostElement = new Map<any, AnimationTransitionNamespace>();
|
public namespacesByHostElement = new Map<any, AnimationTransitionNamespace>();
|
||||||
public collectedEnterElements: any[] = [];
|
public collectedEnterElements: any[] = [];
|
||||||
|
public collectedLeaveElements: any[] = [];
|
||||||
|
|
||||||
// this method is designed to be overridden by the code that uses this engine
|
// this method is designed to be overridden by the code that uses this engine
|
||||||
public onRemovalComplete = (element: any, context: any) => {};
|
public onRemovalComplete = (element: any, context: any) => {};
|
||||||
@ -572,8 +583,9 @@ export class TransitionAnimationEngine {
|
|||||||
|
|
||||||
// special case for when an element is removed and reinserted (move operation)
|
// special case for when an element is removed and reinserted (move operation)
|
||||||
// when this occurs we do not want to use the element for deletion later
|
// when this occurs we do not want to use the element for deletion later
|
||||||
if (this.queuedRemovals.has(element)) {
|
const details = element[REMOVAL_FLAG] as ElementAnimationState;
|
||||||
this.queuedRemovals.delete(element);
|
if (details && details.setForRemoval) {
|
||||||
|
details.setForRemoval = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// in the event that the namespaceId is blank then the caller
|
// in the event that the namespaceId is blank then the caller
|
||||||
@ -592,19 +604,27 @@ export class TransitionAnimationEngine {
|
|||||||
collectEnterElement(element: any) { this.collectedEnterElements.push(element); }
|
collectEnterElement(element: any) { this.collectedEnterElements.push(element); }
|
||||||
|
|
||||||
removeNode(namespaceId: string, element: any, context: any, doNotRecurse?: boolean): void {
|
removeNode(namespaceId: string, element: any, context: any, doNotRecurse?: boolean): void {
|
||||||
if (namespaceId) {
|
if (!isElementNode(element)) {
|
||||||
const ns = this._fetchNamespace(namespaceId);
|
this._onRemovalComplete(element, context);
|
||||||
if (!isElementNode(element) || !ns) {
|
return;
|
||||||
this._onRemovalComplete(element, context);
|
}
|
||||||
} else {
|
|
||||||
ns.removeNode(element, context, doNotRecurse);
|
const ns = namespaceId ? this._fetchNamespace(namespaceId) : null;
|
||||||
}
|
if (ns) {
|
||||||
|
ns.removeNode(element, context, doNotRecurse);
|
||||||
} else {
|
} else {
|
||||||
this.queuedRemovals.set(element, () => this._onRemovalComplete(element, context));
|
this.markElementAsRemoved(namespaceId, element, false, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
markElementAsRemoved(element: any) { this.queuedRemovals.set(element, NOOP_FN); }
|
markElementAsRemoved(namespaceId: string, element: any, hasAnimation?: boolean, context?: any) {
|
||||||
|
this.collectedLeaveElements.push(element);
|
||||||
|
element[REMOVAL_FLAG] = {
|
||||||
|
namespaceId,
|
||||||
|
setForRemoval: context, hasAnimation,
|
||||||
|
removedBeforeQueried: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
listen(
|
listen(
|
||||||
namespaceId: string, element: any, name: string, phase: string,
|
namespaceId: string, element: any, name: string, phase: string,
|
||||||
@ -653,6 +673,22 @@ export class TransitionAnimationEngine {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
processLeaveNode(element: any) {
|
||||||
|
const details = element[REMOVAL_FLAG] as ElementAnimationState;
|
||||||
|
if (details && details.setForRemoval) {
|
||||||
|
// this will prevent it from removing it twice
|
||||||
|
element[REMOVAL_FLAG] = NULL_REMOVAL_STATE;
|
||||||
|
if (details.namespaceId) {
|
||||||
|
this.destroyInnerAnimations(element);
|
||||||
|
const ns = this._fetchNamespace(details.namespaceId);
|
||||||
|
if (ns) {
|
||||||
|
ns.clearElementCache(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._onRemovalComplete(element, details.setForRemoval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
flush(microtaskId: number = -1) {
|
flush(microtaskId: number = -1) {
|
||||||
let players: AnimationPlayer[] = [];
|
let players: AnimationPlayer[] = [];
|
||||||
if (this.newHostElements.size) {
|
if (this.newHostElements.size) {
|
||||||
@ -660,15 +696,19 @@ export class TransitionAnimationEngine {
|
|||||||
this.newHostElements.clear();
|
this.newHostElements.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._namespaceList.length && (this.totalQueuedPlayers || this.queuedRemovals.size)) {
|
if (this._namespaceList.length &&
|
||||||
|
(this.totalQueuedPlayers || this.collectedLeaveElements.length)) {
|
||||||
players = this._flushAnimations(microtaskId);
|
players = this._flushAnimations(microtaskId);
|
||||||
} else {
|
} else {
|
||||||
this.queuedRemovals.forEach(fn => fn());
|
for (let i = 0; i < this.collectedLeaveElements.length; i++) {
|
||||||
|
const element = this.collectedLeaveElements[i];
|
||||||
|
this.processLeaveNode(element);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.totalQueuedPlayers = 0;
|
this.totalQueuedPlayers = 0;
|
||||||
this.collectedEnterElements.length = 0;
|
this.collectedEnterElements.length = 0;
|
||||||
this.queuedRemovals.clear();
|
this.collectedLeaveElements.length = 0;
|
||||||
this._flushFns.forEach(fn => fn());
|
this._flushFns.forEach(fn => fn());
|
||||||
this._flushFns = [];
|
this._flushFns = [];
|
||||||
|
|
||||||
@ -704,7 +744,17 @@ export class TransitionAnimationEngine {
|
|||||||
const enterNodes: any[] =
|
const enterNodes: any[] =
|
||||||
allEnterNodes.length ? collectEnterElements(this.driver, allEnterNodes) : [];
|
allEnterNodes.length ? collectEnterElements(this.driver, allEnterNodes) : [];
|
||||||
|
|
||||||
this.queuedRemovals.forEach((fn, element) => addClass(element, LEAVE_CLASSNAME));
|
const leaveNodes: any[] = [];
|
||||||
|
for (let i = 0; i < this.collectedLeaveElements.length; i++) {
|
||||||
|
const element = this.collectedLeaveElements[i];
|
||||||
|
if (isElementNode(element)) {
|
||||||
|
const details = element[REMOVAL_FLAG] as ElementAnimationState;
|
||||||
|
if (details && details.setForRemoval) {
|
||||||
|
addClass(element, LEAVE_CLASSNAME);
|
||||||
|
leaveNodes.push(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = this._namespaceList.length - 1; i >= 0; i--) {
|
for (let i = this._namespaceList.length - 1; i >= 0; i--) {
|
||||||
const ns = this._namespaceList[i];
|
const ns = this._namespaceList[i];
|
||||||
@ -788,10 +838,6 @@ export class TransitionAnimationEngine {
|
|||||||
|
|
||||||
allPreviousPlayersMap.forEach(players => players.forEach(player => player.destroy()));
|
allPreviousPlayersMap.forEach(players => players.forEach(player => player.destroy()));
|
||||||
|
|
||||||
const leaveNodes: any[] = bodyNode && allPostStyleElements.size ?
|
|
||||||
this.driver.query(bodyNode, LEAVE_SELECTOR, true) :
|
|
||||||
[];
|
|
||||||
|
|
||||||
// PRE STAGE: fill the ! styles
|
// PRE STAGE: fill the ! styles
|
||||||
const preStylesMap = allPreStyleElements.size ?
|
const preStylesMap = allPreStyleElements.size ?
|
||||||
cloakAndComputeStyles(this.driver, enterNodes, allPreStyleElements, PRE_STYLE) :
|
cloakAndComputeStyles(this.driver, enterNodes, allPreStyleElements, PRE_STYLE) :
|
||||||
@ -861,14 +907,18 @@ export class TransitionAnimationEngine {
|
|||||||
// run through all of the queued removals and see if they
|
// run through all of the queued removals and see if they
|
||||||
// were picked up by a query. If not then perform the removal
|
// were picked up by a query. If not then perform the removal
|
||||||
// operation right away unless a parent animation is ongoing.
|
// operation right away unless a parent animation is ongoing.
|
||||||
this.queuedRemovals.forEach((fn, element) => {
|
for (let i = 0; i < leaveNodes.length; i++) {
|
||||||
|
const element = leaveNodes[i];
|
||||||
const players = queriedElements.get(element);
|
const players = queriedElements.get(element);
|
||||||
if (players) {
|
if (players) {
|
||||||
optimizeGroupPlayer(players).onDone(fn);
|
removeNodesAfterAnimationDone(this, element, players);
|
||||||
} else {
|
} else {
|
||||||
fn();
|
const details = element[REMOVAL_FLAG] as ElementAnimationState;
|
||||||
|
if (details && !details.hasAnimation) {
|
||||||
|
this.processLeaveNode(element);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
rootPlayers.forEach(player => {
|
rootPlayers.forEach(player => {
|
||||||
this.players.push(player);
|
this.players.push(player);
|
||||||
@ -888,7 +938,8 @@ export class TransitionAnimationEngine {
|
|||||||
|
|
||||||
elementContainsData(namespaceId: string, element: any) {
|
elementContainsData(namespaceId: string, element: any) {
|
||||||
let containsData = false;
|
let containsData = false;
|
||||||
if (this.queuedRemovals.has(element)) containsData = true;
|
const details = element[REMOVAL_FLAG] as ElementAnimationState;
|
||||||
|
if (details && details.setForRemoval) containsData = true;
|
||||||
if (this.playersByElement.has(element)) containsData = true;
|
if (this.playersByElement.has(element)) containsData = true;
|
||||||
if (this.playersByQueriedElement.has(element)) containsData = true;
|
if (this.playersByQueriedElement.has(element)) containsData = true;
|
||||||
if (this.statesByElement.has(element)) containsData = true;
|
if (this.statesByElement.has(element)) containsData = true;
|
||||||
@ -979,7 +1030,8 @@ export class TransitionAnimationEngine {
|
|||||||
const element = timelineInstruction.element;
|
const element = timelineInstruction.element;
|
||||||
|
|
||||||
// FIXME (matsko): make sure to-be-removed animations are removed properly
|
// FIXME (matsko): make sure to-be-removed animations are removed properly
|
||||||
if (element['REMOVED']) return new NoopAnimationPlayer();
|
const details = element[REMOVAL_FLAG];
|
||||||
|
if (details && details.removedBeforeQueried) return new NoopAnimationPlayer();
|
||||||
|
|
||||||
const isQueriedElement = element !== rootElement;
|
const isQueriedElement = element !== rootElement;
|
||||||
let previousPlayers: AnimationPlayer[] = EMPTY_PLAYER_ARRAY;
|
let previousPlayers: AnimationPlayer[] = EMPTY_PLAYER_ARRAY;
|
||||||
@ -1229,7 +1281,7 @@ function cloakAndComputeStyles(
|
|||||||
// there is no easy way to detect this because a sub element could be removed
|
// there is no easy way to detect this because a sub element could be removed
|
||||||
// by a parent animation element being detached.
|
// by a parent animation element being detached.
|
||||||
if (!value || value.length == 0) {
|
if (!value || value.length == 0) {
|
||||||
element['REMOVED'] = true;
|
element[REMOVAL_FLAG] = NULL_REMOVED_QUERIED_STATE;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
valuesMap.set(element, styles);
|
valuesMap.set(element, styles);
|
||||||
@ -1280,17 +1332,14 @@ function removeClass(element: any, className: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setAttribute(element: any, attr: string, value: any) {
|
|
||||||
if (element.setAttribute) {
|
|
||||||
element.setAttribute(attr, value);
|
|
||||||
} else {
|
|
||||||
element[attr] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBodyNode(): any|null {
|
function getBodyNode(): any|null {
|
||||||
if (typeof document != 'undefined') {
|
if (typeof document != 'undefined') {
|
||||||
return document.body;
|
return document.body;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeNodesAfterAnimationDone(
|
||||||
|
engine: TransitionAnimationEngine, element: any, players: AnimationPlayer[]) {
|
||||||
|
optimizeGroupPlayer(players).onDone(() => engine.processLeaveNode(element));
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user