From e45f3029340db718b6ed7e91b5d14f5cf14cd71d Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 24 Aug 2023 13:42:12 -0500 Subject: [PATCH] Update built-in view transitions (#8207) * feat: rename morph => initial * feat: update slide, fade animations, add none * chore: add changeset * fix: bump compiler * feat: disable root transition by default * chore: update changeset * chore: fix build * feat(transitions): crossfade => fade * feat(transitions): remove opinionated default * chore: update changeset * feat(transitions): set root to fade * feat: remove opinionated root style * chore: remove unused easings * feat: refactor transition logic, ensure defaults are wrapped in @layer * Update .changeset/five-geese-crash.md Co-authored-by: Sarah Rainsberger * Update .changeset/five-geese-crash.md Co-authored-by: Sarah Rainsberger * Update .changeset/five-geese-crash.md Co-authored-by: Sarah Rainsberger * Update five-geese-crash.md --------- Co-authored-by: Sarah Rainsberger --- .changeset/five-geese-crash.md | 11 ++ .../src/components/Layout.astro | 2 +- packages/astro/package.json | 2 +- packages/astro/src/@types/astro.ts | 4 +- packages/astro/src/core/compile/compile.ts | 2 - .../astro/src/runtime/server/transition.ts | 112 +++++++++--------- packages/astro/src/transitions/index.ts | 34 +++--- pnpm-lock.yaml | 8 +- 8 files changed, 93 insertions(+), 82 deletions(-) create mode 100644 .changeset/five-geese-crash.md diff --git a/.changeset/five-geese-crash.md b/.changeset/five-geese-crash.md new file mode 100644 index 000000000000..832d3dacafdc --- /dev/null +++ b/.changeset/five-geese-crash.md @@ -0,0 +1,11 @@ +--- +'astro': major +--- + +Change the [View Transition built-in animation](https://docs.astro.build/en/guides/view-transitions/#built-in-animation-directives) options. + +The `transition:animate` value `morph` has been renamed to `initial`. Also, this is no longer the default animation. + +If no `transition:animate` directive is specified, your animations will now default to `fade`. + +Astro also supports a new `transition:animate` value, `none`. This value can be used on a page's `` element to disable animated full-page transitions on an entire page. diff --git a/packages/astro/e2e/fixtures/view-transitions/src/components/Layout.astro b/packages/astro/e2e/fixtures/view-transitions/src/components/Layout.astro index 1c0f9bbdf0c1..1dc1a1c248a7 100644 --- a/packages/astro/e2e/fixtures/view-transitions/src/components/Layout.astro +++ b/packages/astro/e2e/fixtures/view-transitions/src/components/Layout.astro @@ -32,7 +32,7 @@ const { link } = Astro.props as Props; -
+

testing

diff --git a/packages/astro/package.json b/packages/astro/package.json index 166801bc0f8a..0ccc7435e4b0 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -119,7 +119,7 @@ "test:e2e:match": "playwright test -g" }, "dependencies": { - "@astrojs/compiler": "^2.0.0", + "@astrojs/compiler": "^2.0.1", "@astrojs/internal-helpers": "workspace:*", "@astrojs/markdown-remark": "workspace:*", "@astrojs/telemetry": "workspace:*", diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index e6dfd029e18d..368a8713905a 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -77,7 +77,7 @@ export interface TransitionDirectionalAnimations { backwards: TransitionAnimationPair; } -export type TransitionAnimationValue = 'morph' | 'slide' | 'fade' | TransitionDirectionalAnimations; +export type TransitionAnimationValue = 'initial' | 'slide' | 'fade' | 'none' | TransitionDirectionalAnimations; // Allow users to extend this for astro-jsx.d.ts // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -93,7 +93,7 @@ export interface AstroBuiltinAttributes { 'set:html'?: any; 'set:text'?: any; 'is:raw'?: boolean; - 'transition:animate'?: 'morph' | 'slide' | 'fade' | TransitionDirectionalAnimations; + 'transition:animate'?: TransitionAnimationValue; 'transition:name'?: string; 'transition:persist'?: boolean | string; } diff --git a/packages/astro/src/core/compile/compile.ts b/packages/astro/src/core/compile/compile.ts index bd069611df84..d66a2d9c6980 100644 --- a/packages/astro/src/core/compile/compile.ts +++ b/packages/astro/src/core/compile/compile.ts @@ -45,8 +45,6 @@ export async function compile({ astroGlobalArgs: JSON.stringify(astroConfig.site), scopedStyleStrategy: astroConfig.scopedStyleStrategy, resultScopedSlot: true, - experimentalTransitions: astroConfig.experimental.viewTransitions, - experimentalPersistence: astroConfig.experimental.viewTransitions, transitionsAnimationURL: 'astro/components/viewtransitions.css', preprocessStyle: createStylePreprocessor({ filename, diff --git a/packages/astro/src/runtime/server/transition.ts b/packages/astro/src/runtime/server/transition.ts index c348d292daf3..fd01ff5c0824 100644 --- a/packages/astro/src/runtime/server/transition.ts +++ b/packages/astro/src/runtime/server/transition.ts @@ -2,7 +2,6 @@ import type { SSRResult, TransitionAnimation, TransitionAnimationValue, - TransitionDirectionalAnimations, } from '../../@types/astro'; import { fade, slide } from '../../transitions/index.js'; import { markHTMLString } from './escape.js'; @@ -22,78 +21,80 @@ export function createTransitionScope(result: SSRResult, hash: string) { return `astro-${hash}-${num}`; } +// Ensure animationName is a valid CSS identifier +function toValidIdent(name: string): string { + return name.replace(/[^a-zA-Z0-9\-\_]/g, '_').replace(/^\_+|\_+$/g, '') +} + +type Entries> = Iterable<[keyof T, T[keyof T]]> + +const getAnimations = (name: TransitionAnimationValue) => { + if (name === 'fade') return fade(); + if (name === 'slide') return slide(); + if (typeof name === 'object') return name; +} + export function renderTransition( result: SSRResult, hash: string, animationName: TransitionAnimationValue | undefined, transitionName: string ) { - let animations: TransitionDirectionalAnimations | null = null; - switch (animationName) { - case 'fade': { - animations = fade(); - break; - } - case 'slide': { - animations = slide(); - break; - } - default: { - if (typeof animationName === 'object') { - animations = animationName; + // Default to `fade` (similar to `initial`, but snappier) + if (!animationName) animationName = 'fade'; + const scope = createTransitionScope(result, hash); + const name = transitionName ? toValidIdent(transitionName) : scope; + const sheet = new ViewTransitionStyleSheet(scope, name); + + const animations = getAnimations(animationName); + if (animations) { + for (const [direction, images] of Object.entries(animations) as Entries) { + for (const [image, rules] of Object.entries(images) as Entries) { + sheet.addAnimationPair(direction, image, rules); } } + } else if (animationName === 'none') { + sheet.addAnimationRaw('old', 'animation: none; opacity: 0; mix-blend-mode: normal;') + sheet.addAnimationRaw('new', 'animation: none; mix-blend-mode: normal;') } - const scope = createTransitionScope(result, hash); + result._metadata.extraHead.push(markHTMLString(``)); + return scope; +} - // Default transition name is the scope of the element, ie HASH-1 - if (!transitionName) { - transitionName = scope; - } +class ViewTransitionStyleSheet { + private modern: string[] = [] + private fallback: string[] = [] - const styles = markHTMLString(``); - result._metadata.extraHead.push(styles); + addAnimationRaw(image: 'old' | 'new' | 'group', animation: string) { + const { scope, name } = this; + this.addRule('modern', `::view-transition-${image}(${name}) { ${animation} }`) + this.addRule('fallback', `[data-astro-transition-fallback="${image}"] [data-astro-transition-scope="${scope}"] { ${animation} }`) + } - return scope; + addAnimationPair(direction: 'forwards' | 'backwards', image: 'old' | 'new', rules: TransitionAnimation | TransitionAnimation[]) { + const { scope, name } = this; + const animation = stringifyAnimation(rules); + const prefix = direction === 'backwards' ? `[data-astro-transition=back]` : ''; + this.addRule('modern', `${prefix}::view-transition-${image}(${name}) { ${animation} }`) + this.addRule('fallback', `${prefix}[data-astro-transition-fallback="${image}"] [data-astro-transition-scope="${scope}"] { ${animation} }`) + } } type AnimationBuilder = { @@ -137,7 +138,6 @@ function stringifyAnimations(anims: TransitionAnimation[]): string { const builder = animationBuilder(); for (const anim of anims) { - /*300ms cubic-bezier(0.4, 0, 0.2, 1) both astroSlideFromRight;*/ if (anim.duration) { addAnimationProperty(builder, 'animation-duration', toTimeValue(anim.duration)); } diff --git a/packages/astro/src/transitions/index.ts b/packages/astro/src/transitions/index.ts index ff3aee08c98b..b218783317f6 100644 --- a/packages/astro/src/transitions/index.ts +++ b/packages/astro/src/transitions/index.ts @@ -1,5 +1,7 @@ import type { TransitionAnimationPair, TransitionDirectionalAnimations } from '../@types/astro'; +const EASE_IN_OUT_QUART = 'cubic-bezier(0.76, 0, 0.24, 1)'; + export function slide({ duration, }: { @@ -11,13 +13,13 @@ export function slide({ { name: 'astroFadeOut', duration: duration ?? '90ms', - easing: 'cubic-bezier(0.4, 0, 1, 1)', + easing: EASE_IN_OUT_QUART, fillMode: 'both', }, { name: 'astroSlideToLeft', - duration: duration ?? '300ms', - easing: 'cubic-bezier(0.4, 0, 0.2, 1)', + duration: duration ?? '220ms', + easing: EASE_IN_OUT_QUART, fillMode: 'both', }, ], @@ -25,14 +27,14 @@ export function slide({ { name: 'astroFadeIn', duration: duration ?? '210ms', - easing: 'cubic-bezier(0, 0, 0.2, 1)', - delay: '90ms', + easing: EASE_IN_OUT_QUART, + delay: duration ? undefined : '30ms', fillMode: 'both', }, { name: 'astroSlideFromRight', - duration: duration ?? '300ms', - easing: 'cubic-bezier(0.4, 0, 0.2, 1)', + duration: duration ?? '220ms', + easing: EASE_IN_OUT_QUART, fillMode: 'both', }, ], @@ -45,22 +47,22 @@ export function slide({ } export function fade({ - duration, + duration }: { duration?: string | number; } = {}): TransitionDirectionalAnimations { const anim = { old: { - name: 'astroFadeInOut', - duration: duration ?? '0.2s', - easing: 'linear', - fillMode: 'forwards', + name: 'astroFadeOut', + duration: duration ?? 180, + easing: EASE_IN_OUT_QUART, + fillMode: 'both', }, new: { - name: 'astroFadeInOut', - duration: duration ?? '0.3s', - easing: 'linear', - fillMode: 'backwards', + name: 'astroFadeIn', + duration: duration ?? 180, + easing: EASE_IN_OUT_QUART, + fillMode: 'both', }, } satisfies TransitionAnimationPair; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 71462913014d..4332ad60a5a1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -483,8 +483,8 @@ importers: packages/astro: dependencies: '@astrojs/compiler': - specifier: ^2.0.0 - version: 2.0.0 + specifier: ^2.0.1 + version: 2.0.1 '@astrojs/internal-helpers': specifier: workspace:* version: link:../internal-helpers @@ -5161,8 +5161,8 @@ packages: resolution: {integrity: sha512-o/ObKgtMzl8SlpIdzaxFnt7SATKPxu4oIP/1NL+HDJRzxfJcAkOTAb/ZKMRyULbz4q+1t2/DAebs2Z1QairkZw==} dev: true - /@astrojs/compiler@2.0.0: - resolution: {integrity: sha512-SKVWorXpOHff+OuZCd5kdTc5HxVX7bVXVXYP0jANT4crz7y2PdthUxMnE21iuYt4+Bq3aV5MId4OdgwlJ2/d/Q==} + /@astrojs/compiler@2.0.1: + resolution: {integrity: sha512-DfBR7Cf+tOgQ4n7TIgTtU5x5SEA/08DNshpEPcT+91A0KbBlmUOYMBM/O6qAaHkmVo1KIoXQYhAmfdTT1zx9PQ==} dev: false /@astrojs/language-server@2.3.0(prettier-plugin-astro@0.12.0)(prettier@3.0.2)(typescript@5.1.6):