diff options
| author | littleblack111 <littleblack11111@gmail.com> | 2024-08-30 17:21:18 +0800 |
|---|---|---|
| committer | littleblack111 <littleblack11111@gmail.com> | 2024-08-30 17:21:18 +0800 |
| commit | efbc2ffe6a1ee5ec38a4396305561d6515d0943e (patch) | |
| tree | 2b4c94f18f2c792dfda813050755d5c0273fae22 /node_modules/swup/src/modules/awaitAnimations.ts | |
| parent | 43acceb75957c880a5ba5a31aae221394a36d5f6 (diff) | |
node fixes, updates
Diffstat (limited to 'node_modules/swup/src/modules/awaitAnimations.ts')
| -rw-r--r-- | node_modules/swup/src/modules/awaitAnimations.ts | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/node_modules/swup/src/modules/awaitAnimations.ts b/node_modules/swup/src/modules/awaitAnimations.ts new file mode 100644 index 00000000..e4b77e74 --- /dev/null +++ b/node_modules/swup/src/modules/awaitAnimations.ts @@ -0,0 +1,148 @@ +import { queryAll } from '../utils.js'; +import type Swup from '../Swup.js'; +import type { Options } from '../Swup.js'; + +const TRANSITION = 'transition'; +const ANIMATION = 'animation'; + +type AnimationType = typeof TRANSITION | typeof ANIMATION; +type AnimationEndEvent = `${AnimationType}end`; +type AnimationProperty = 'Delay' | 'Duration'; +type AnimationStyleKey = `${AnimationType}${AnimationProperty}` | 'transitionProperty'; + +export type AnimationDirection = 'in' | 'out'; + +/** + * Return a Promise that resolves when all CSS animations and transitions + * are done on the page. Filters by selector or takes elements directly. + */ +export async function awaitAnimations( + this: Swup, + { + selector, + elements + }: { + selector: Options['animationSelector']; + elements?: NodeListOf<HTMLElement> | HTMLElement[]; + } +): Promise<void> { + // Allow usage of swup without animations: { animationSelector: false } + if (selector === false && !elements) { + return; + } + + // Allow passing in elements + let animatedElements: HTMLElement[] = []; + if (elements) { + animatedElements = Array.from(elements); + } else if (selector) { + animatedElements = queryAll(selector, document.body); + // Warn if no elements match the selector, but keep things going + if (!animatedElements.length) { + console.warn(`[swup] No elements found matching animationSelector \`${selector}\``); + return; + } + } + + const awaitedAnimations = animatedElements.map((el) => awaitAnimationsOnElement(el)); + const hasAnimations = awaitedAnimations.filter(Boolean).length > 0; + if (!hasAnimations) { + if (selector) { + console.warn( + `[swup] No CSS animation duration defined on elements matching \`${selector}\`` + ); + } + return; + } + + await Promise.all(awaitedAnimations); +} + +function awaitAnimationsOnElement(element: HTMLElement): Promise<void> | false { + const { type, timeout, propCount } = getTransitionInfo(element); + + // Resolve immediately if no transition defined + if (!type || !timeout) { + return false; + } + + return new Promise((resolve) => { + const endEvent: AnimationEndEvent = `${type}end`; + const startTime = performance.now(); + let propsTransitioned = 0; + + const end = () => { + element.removeEventListener(endEvent, onEnd); + resolve(); + }; + + const onEnd = (event: TransitionEvent | AnimationEvent) => { + // Skip transitions on child elements + if (event.target !== element) { + return; + } + + // Skip transitions that happened before we started listening + const elapsedTime = (performance.now() - startTime) / 1000; + if (elapsedTime < event.elapsedTime) { + return; + } + + // End if all properties have transitioned + if (++propsTransitioned >= propCount) { + end(); + } + }; + + setTimeout(() => { + if (propsTransitioned < propCount) { + end(); + } + }, timeout + 1); + + element.addEventListener(endEvent, onEnd); + }); +} + +function getTransitionInfo(element: Element) { + const styles = window.getComputedStyle(element); + + const transitionDelays = getStyleProperties(styles, `${TRANSITION}Delay`); + const transitionDurations = getStyleProperties(styles, `${TRANSITION}Duration`); + const transitionTimeout = calculateTimeout(transitionDelays, transitionDurations); + + const animationDelays = getStyleProperties(styles, `${ANIMATION}Delay`); + const animationDurations = getStyleProperties(styles, `${ANIMATION}Duration`); + const animationTimeout = calculateTimeout(animationDelays, animationDurations); + + const timeout = Math.max(transitionTimeout, animationTimeout); + const type: AnimationType | null = + timeout > 0 ? (transitionTimeout > animationTimeout ? TRANSITION : ANIMATION) : null; + const propCount = type + ? type === TRANSITION + ? transitionDurations.length + : animationDurations.length + : 0; + + return { + type, + timeout, + propCount + }; +} + +export function getStyleProperties(styles: CSSStyleDeclaration, key: AnimationStyleKey): string[] { + return (styles[key] || '').split(', '); +} + +export function calculateTimeout(delays: string[], durations: string[]): number { + while (delays.length < durations.length) { + delays = delays.concat(delays); + } + + return Math.max(...durations.map((duration, i) => toMs(duration) + toMs(delays[i]))); +} + +export function toMs(time: string): number { + return parseFloat(time) * 1000; +} |
