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;
-
+
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):