Skip to content

Commit

Permalink
[Web LA] Start exiting after position fix (#6308)
Browse files Browse the repository at this point in the history
## Summary

While preparing presentation about web LA I've found out that `exiting`
animation has strange order of operations - first animation is started
and then position of the element is adjusted (by scroll and other
factors). In theory, this may lead to flickers so I believe it will be
better to start animation after we adjust position.

## Test plan

Tested on example app
  • Loading branch information
m-bert authored Jul 22, 2024
1 parent 72b761a commit 976a4b8
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import type { FlatList, FlatListProps } from 'react-native';
import { addHTMLMutationObserver } from '../layoutReanimation/web/domUtils';
import { getViewInfo } from './getViewInfo';
import { NativeEventsManager } from './NativeEventsManager';
import type { ReanimatedHTMLElement } from '../js-reanimated';

const IS_WEB = isWeb();

Expand Down Expand Up @@ -178,7 +179,7 @@ export function createAnimatedComponent(

startWebLayoutAnimation(
this.props,
this._component as HTMLElement,
this._component as ReanimatedHTMLElement,
LayoutAnimationType.ENTERING
);
}
Expand Down Expand Up @@ -211,7 +212,7 @@ export function createAnimatedComponent(

startWebLayoutAnimation(
this.props,
this._component as HTMLElement,
this._component as ReanimatedHTMLElement,
LayoutAnimationType.EXITING
);
} else if (exiting && !IS_WEB && !isFabric()) {
Expand Down Expand Up @@ -418,7 +419,7 @@ export function createAnimatedComponent(
) {
tryActivateLayoutTransition(
this.props,
this._component as HTMLElement,
this._component as ReanimatedHTMLElement,
snapshot
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import type { TransitionData } from './animationParser';
import { Keyframe } from '../animationBuilder';
import { makeElementVisible } from './componentStyle';
import { EasingNameSymbol } from '../../Easing';
import type { ReanimatedHTMLElement } from '../../js-reanimated';

function chooseConfig<ComponentProps extends Record<string, unknown>>(
animationType: LayoutAnimationType,
Expand Down Expand Up @@ -94,7 +95,7 @@ function maybeReportOverwrittenProperties(
function chooseAction(
animationType: LayoutAnimationType,
animationConfig: AnimationConfig,
element: HTMLElement,
element: ReanimatedHTMLElement,
transitionData: TransitionData
) {
switch (animationType) {
Expand Down Expand Up @@ -183,7 +184,7 @@ export function startWebLayoutAnimation<
ComponentProps extends Record<string, unknown>
>(
props: Readonly<AnimatedComponentProps<ComponentProps>>,
element: HTMLElement,
element: ReanimatedHTMLElement,
animationType: LayoutAnimationType,
transitionData?: TransitionData
) {
Expand Down Expand Up @@ -214,7 +215,7 @@ export function tryActivateLayoutTransition<
ComponentProps extends Record<string, unknown>
>(
props: Readonly<AnimatedComponentProps<ComponentProps>>,
element: HTMLElement,
element: ReanimatedHTMLElement,
snapshot: DOMRect
) {
if (!props.layout) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,10 @@ export function saveSnapshot(element: HTMLElement) {
}

export function setElementAnimation(
element: HTMLElement,
element: ReanimatedHTMLElement,
animationConfig: AnimationConfig,
shouldSavePosition = false
shouldSavePosition = false,
parent: Element | null = null
) {
const { animationName, duration, delay, easing } = animationConfig;

Expand All @@ -179,22 +180,30 @@ export function setElementAnimation(
saveSnapshot(element);
}

if (parent?.contains(element)) {
element.removedAfterAnimation = true;
parent.removeChild(element);
}

animationConfig.callback?.(true);
element.removeEventListener('animationcancel', animationCancelHandler);
};

const animationCancelHandler = () => {
animationConfig.callback?.(false);

if (parent?.contains(element)) {
element.removedAfterAnimation = true;
parent.removeChild(element);
}

element.removeEventListener('animationcancel', animationCancelHandler);
};

// Here we have to use `addEventListener` since element.onanimationcancel doesn't work on chrome
element.onanimationstart = () => {
if (animationConfig.animationType === LayoutAnimationType.ENTERING) {
_updatePropsJS(
{ visibility: 'initial' },
element as ReanimatedHTMLElement
);
_updatePropsJS({ visibility: 'initial' }, element);
}

element.addEventListener('animationcancel', animationCancelHandler);
Expand All @@ -210,7 +219,7 @@ export function setElementAnimation(
}

export function handleLayoutTransition(
element: HTMLElement,
element: ReanimatedHTMLElement,
animationConfig: AnimationConfig,
transitionData: TransitionData
) {
Expand Down Expand Up @@ -292,6 +301,7 @@ export function handleExitingAnimation(
dummy.reanimatedDummy = true;

element.style.animationName = '';
dummy.style.animationName = '';

// After cloning the element, we want to move all children from original element to its clone. This is because original element
// will be unmounted, therefore when this code executes in child component, parent will be either empty or removed soon.
Expand All @@ -302,7 +312,6 @@ export function handleExitingAnimation(
dummy.appendChild(element.firstChild);
}

setElementAnimation(dummy, animationConfig);
parent?.appendChild(dummy);

const snapshot = snapshots.get(element)!;
Expand Down Expand Up @@ -332,22 +341,5 @@ export function handleExitingAnimation(

setElementPosition(dummy, snapshot);

const originalOnAnimationEnd = dummy.onanimationend;

dummy.onanimationend = function (event: AnimationEvent) {
if (parent?.contains(dummy)) {
dummy.removedAfterAnimation = true;
parent.removeChild(dummy);
}

// Given that this function overrides onAnimationEnd, it won't be null
originalOnAnimationEnd?.call(this, event);
};

dummy.addEventListener('animationcancel', () => {
if (parent?.contains(dummy)) {
dummy.removedAfterAnimation = true;
parent.removeChild(dummy);
}
});
setElementAnimation(dummy, animationConfig, false, parent);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use strict';
import type { ReanimatedHTMLElement } from '../../../js-reanimated';
import { LayoutAnimationType } from '../..';
import type { WebEasingsNames } from '../Easing.web';
import { getEasingByName } from '../Easing.web';
Expand Down Expand Up @@ -33,8 +34,8 @@ function showChildren(
}

function prepareParent(
element: HTMLElement,
dummy: HTMLElement,
element: ReanimatedHTMLElement,
dummy: ReanimatedHTMLElement,
animationConfig: AnimationConfig,
transitionData: TransitionData
) {
Expand Down Expand Up @@ -76,7 +77,7 @@ function prepareParent(
}

function prepareDummy(
element: HTMLElement,
element: ReanimatedHTMLElement,
animationConfig: AnimationConfig,
transitionData: TransitionData,
dummyTransitionKeyframeName: string
Expand All @@ -91,14 +92,14 @@ function prepareDummy(
reversed: false,
};

const dummy = element.cloneNode(true) as HTMLElement;
const dummy = element.cloneNode(true) as ReanimatedHTMLElement;
resetStyle(dummy);

return { dummy, dummyAnimationConfig };
}

export function prepareCurvedTransition(
element: HTMLElement,
element: ReanimatedHTMLElement,
animationConfig: AnimationConfig,
transitionData: TransitionData,
dummyTransitionKeyframeName: string
Expand Down

0 comments on commit 976a4b8

Please sign in to comment.