diff --git a/.changeset/strong-needles-accept.md b/.changeset/strong-needles-accept.md new file mode 100644 index 000000000000..4546d988b171 --- /dev/null +++ b/.changeset/strong-needles-accept.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Allow fallback animations on html element diff --git a/packages/astro/components/ViewTransitions.astro b/packages/astro/components/ViewTransitions.astro index 7fa33676825a..33741d535b4e 100644 --- a/packages/astro/components/ViewTransitions.astro +++ b/packages/astro/components/ViewTransitions.astro @@ -220,23 +220,15 @@ const { fallback = 'animate' } = Astro.props as Props; links.length && (await Promise.all(links)); if (fallback === 'animate') { - let isAnimating = false; - addEventListener('animationstart', () => (isAnimating = true), { once: true }); - // Trigger the animations document.documentElement.dataset.astroTransitionFallback = 'old'; + const finished = Promise.all(document.getAnimations().map(a => a.finished)); const fallbackSwap = () => { - removeEventListener('animationend', fallbackSwap); - clearTimeout(timeout); swap(); document.documentElement.dataset.astroTransitionFallback = 'new'; }; - // If there are any animations, want for the animationend event. - addEventListener('animationend', fallbackSwap, { once: true }); - // If there are no animations, go ahead and swap on next tick - // This is necessary because we do not know if there are animations. - // The setTimeout is a fallback in case there are none. - let timeout = setTimeout(() => !isAnimating && fallbackSwap()); + await finished; + fallbackSwap(); } else { swap(); } diff --git a/packages/astro/src/runtime/server/transition.ts b/packages/astro/src/runtime/server/transition.ts index 88a41d9ae9cb..4a13c8126f95 100644 --- a/packages/astro/src/runtime/server/transition.ts +++ b/packages/astro/src/runtime/server/transition.ts @@ -52,7 +52,8 @@ export function renderTransition( } } } else if (animationName === 'none') { - sheet.addAnimationRaw('old', 'animation: none; opacity: 0; mix-blend-mode: normal;'); + sheet.addFallback('old', 'animation: none; mix-blend-mode: normal;'); + sheet.addModern('old', 'animation: none; opacity: 0; mix-blend-mode: normal;'); sheet.addAnimationRaw('new', 'animation: none; mix-blend-mode: normal;'); } @@ -88,11 +89,22 @@ class ViewTransitionStyleSheet { } addAnimationRaw(image: 'old' | 'new' | 'group', animation: string) { - const { scope, name } = this; + this.addModern(image, animation); + this.addFallback(image, animation); + } + + addModern(image: 'old' | 'new' | 'group', animation: string) { + const { name } = this; this.addRule('modern', `::view-transition-${image}(${name}) { ${animation} }`); + } + + addFallback(image: 'old' | 'new' | 'group', animation: string) { + const { scope } = this; this.addRule( 'fallback', - `[data-astro-transition-fallback="${image}"] [data-astro-transition-scope="${scope}"] { ${animation} }` + // Two selectors here, the second in case there is an animation on the root. + `[data-astro-transition-fallback="${image}"] [data-astro-transition-scope="${scope}"], + [data-astro-transition-fallback="${image}"][data-astro-transition-scope="${scope}"] { ${animation} }` ); } @@ -107,7 +119,8 @@ class ViewTransitionStyleSheet { this.addRule('modern', `${prefix}::view-transition-${image}(${name}) { ${animation} }`); this.addRule( 'fallback', - `${prefix}[data-astro-transition-fallback="${image}"] [data-astro-transition-scope="${scope}"] { ${animation} }` + `${prefix}[data-astro-transition-fallback="${image}"] [data-astro-transition-scope="${scope}"], + ${prefix}[data-astro-transition-fallback="${image}"][data-astro-transition-scope="${scope}"] { ${animation} }` ); } }