From a77b8267107dec69d244bdfa4c0790ba80b07214 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Wed, 4 Aug 2021 02:35:25 +0300 Subject: [PATCH] [Refactoring] Add `waitForTransitionEnd` and `waitForAnimationEnd` helpers --- src/components/main/Main.tsx | 23 +++------------- src/components/ui/Transition.tsx | 14 +++------- src/util/cssAnimationEndListeners.ts | 40 ++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 30 deletions(-) create mode 100644 src/util/cssAnimationEndListeners.ts diff --git a/src/components/main/Main.tsx b/src/components/main/Main.tsx index 17be5446..a3a87cf3 100644 --- a/src/components/main/Main.tsx +++ b/src/components/main/Main.tsx @@ -22,6 +22,7 @@ import { import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck'; import buildClassName from '../../util/buildClassName'; import { fastRaf } from '../../util/schedulers'; +import { waitForTransitionEnd } from '../../util/cssAnimationEndListeners'; import useShowTransition from '../../hooks/useShowTransition'; import useBackgroundMode from '../../hooks/useBackgroundMode'; import useBeforeUnload from '../../hooks/useBeforeUnload'; @@ -135,17 +136,8 @@ const Main: FC = ({ } const dispatchHeavyAnimationEnd = dispatchHeavyAnimationEvent(); - const middleColumnEl = document.getElementById('MiddleColumn')!; - middleColumnEl.addEventListener('transitionend', function handleTransitionEnd(e: TransitionEvent) { - if (e.target !== e.currentTarget) { - return; - } - - middleColumnEl.removeEventListener('transitionend', handleTransitionEnd); - - dispatchHeavyAnimationEnd(); - }); + waitForTransitionEnd(document.getElementById('MiddleColumn')!, dispatchHeavyAnimationEnd); }, [isLeftColumnShown]); // Dispatch heavy transition event and add body class when opening right column @@ -154,20 +146,13 @@ const Main: FC = ({ return; } - const dispatchHeavyAnimationEnd = dispatchHeavyAnimationEvent(); - const rightColumnEl = document.getElementById('RightColumn')!; - fastRaf(() => { document.body.classList.add('animating-right-column'); }); - rightColumnEl.addEventListener('transitionend', function handleTransitionEnd(e: TransitionEvent) { - if (e.target !== e.currentTarget) { - return; - } - - rightColumnEl.removeEventListener('transitionend', handleTransitionEnd); + const dispatchHeavyAnimationEnd = dispatchHeavyAnimationEvent(); + waitForTransitionEnd(document.getElementById('RightColumn')!, () => { dispatchHeavyAnimationEnd(); fastRaf(() => { diff --git a/src/components/ui/Transition.tsx b/src/components/ui/Transition.tsx index 5419e9ea..1d326588 100644 --- a/src/components/ui/Transition.tsx +++ b/src/components/ui/Transition.tsx @@ -8,6 +8,7 @@ import useForceUpdate from '../../hooks/useForceUpdate'; import usePrevious from '../../hooks/usePrevious'; import buildClassName from '../../util/buildClassName'; import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck'; +import { waitForAnimationEnd } from '../../util/cssAnimationEndListeners'; import './Transition.scss'; @@ -139,8 +140,6 @@ const Transition: FC = ({ requestAnimationFrame(() => { container.classList.add('animating'); - const toNode = childNodes[activeIndex]; - function onAnimationEnd() { requestAnimationFrame(() => { container.classList.remove('animating', 'backwards'); @@ -174,15 +173,8 @@ const Transition: FC = ({ } if (animationLevel > 0) { - toNode.addEventListener('animationend', function handleAnimationEnd(e: AnimationEvent) { - if (e.target !== e.currentTarget) { - return; - } - - toNode.removeEventListener('animationend', handleAnimationEnd as EventListener); - - onAnimationEnd(); - } as EventListener); + const toNode = name === 'mv-slide' ? childNodes[activeIndex].firstChild! : childNodes[activeIndex]; + waitForAnimationEnd(toNode, onAnimationEnd); } else { onAnimationEnd(); } diff --git a/src/util/cssAnimationEndListeners.ts b/src/util/cssAnimationEndListeners.ts new file mode 100644 index 00000000..23fe5147 --- /dev/null +++ b/src/util/cssAnimationEndListeners.ts @@ -0,0 +1,40 @@ +// Sometimes event is fired earlier than animation completes +const ANIMATION_END_DELAY = 50; + +export function waitForTransitionEnd(node: Node, handler: NoneToVoidFunction, propertyName?: string) { + waitForEndEvent('transitionend', node, handler, propertyName); +} + +export function waitForAnimationEnd(node: Node, handler: NoneToVoidFunction, animationName?: string) { + waitForEndEvent('animationend', node, handler, animationName); +} + +function waitForEndEvent( + eventType: 'transitionend' | 'animationend', + node: Node, + handler: NoneToVoidFunction, + detailedName?: string, +) { + let isHandled = false; + + node.addEventListener(eventType, function handleAnimationEnd(e: TransitionEvent | AnimationEvent) { + if (isHandled || e.target !== e.currentTarget) { + return; + } + + if (detailedName && ( + (e instanceof TransitionEvent && e.propertyName === detailedName) + || (e instanceof AnimationEvent && e.animationName === detailedName) + )) { + return; + } + + isHandled = true; + + node.removeEventListener(eventType, handleAnimationEnd as EventListener); + + setTimeout(() => { + handler(); + }, ANIMATION_END_DELAY); + } as EventListener); +}