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 <sarah@rainsberger.ca> * Update .changeset/five-geese-crash.md Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Update .changeset/five-geese-crash.md Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Update five-geese-crash.md --------- Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
This commit is contained in:
parent
c37632a20d
commit
e45f302934
8 changed files with 100 additions and 89 deletions
11
.changeset/five-geese-crash.md
Normal file
11
.changeset/five-geese-crash.md
Normal file
|
@ -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 `<html>` element to disable animated full-page transitions on an entire page.
|
|
@ -32,7 +32,7 @@ const { link } = Astro.props as Props;
|
|||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<header transition:animate="morph">
|
||||
<header transition:animate="initial">
|
||||
<h1>testing</h1>
|
||||
</header>
|
||||
<main transition:animate="slide">
|
||||
|
|
|
@ -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:*",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,80 +21,82 @@ 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<T extends Record<string, any>> = 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<typeof animations>) {
|
||||
for (const [image, rules] of Object.entries(images) as Entries<typeof animations[typeof direction]>) {
|
||||
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);
|
||||
|
||||
// Default transition name is the scope of the element, ie HASH-1
|
||||
if (!transitionName) {
|
||||
transitionName = scope;
|
||||
}
|
||||
|
||||
const styles = markHTMLString(`<style>[data-astro-transition-scope="${scope}"] {
|
||||
view-transition-name: ${transitionName};
|
||||
}
|
||||
${
|
||||
!animations
|
||||
? ``
|
||||
: // Regular animations
|
||||
`
|
||||
::view-transition-old(${transitionName}) {
|
||||
${stringifyAnimation(animations.forwards.old)}
|
||||
}
|
||||
[data-astro-transition-fallback=old] [data-astro-transition-scope="${scope}"] {
|
||||
${stringifyAnimation(animations.forwards.old)}
|
||||
}
|
||||
|
||||
::view-transition-new(${transitionName}) {
|
||||
${stringifyAnimation(animations.forwards.new)}
|
||||
}
|
||||
[data-astro-transition-fallback=new] [data-astro-transition-scope="${scope}"] {
|
||||
${stringifyAnimation(animations.forwards.new)}
|
||||
}
|
||||
|
||||
[data-astro-transition=back]::view-transition-old(${transitionName}) {
|
||||
${stringifyAnimation(animations.backwards.old)}
|
||||
}
|
||||
[data-astro-transition=back][data-astro-transition-fallback=old] [data-astro-transition-scope="${scope}"] {
|
||||
${stringifyAnimation(animations.backwards.old)}
|
||||
}
|
||||
|
||||
[data-astro-transition=back]::view-transition-new(${transitionName}) {
|
||||
${stringifyAnimation(animations.backwards.new)}
|
||||
}
|
||||
[data-astro-transition=back][data-astro-transition-fallback=new] [data-astro-transition-scope="${scope}"] {
|
||||
${stringifyAnimation(animations.backwards.new)}
|
||||
}
|
||||
`.trim()
|
||||
}
|
||||
</style>`);
|
||||
|
||||
result._metadata.extraHead.push(styles);
|
||||
|
||||
result._metadata.extraHead.push(markHTMLString(`<style>${sheet.toString()}</style>`));
|
||||
return scope;
|
||||
}
|
||||
|
||||
class ViewTransitionStyleSheet {
|
||||
private modern: string[] = []
|
||||
private fallback: string[] = []
|
||||
|
||||
constructor(private scope: string, private name: string) {}
|
||||
|
||||
toString() {
|
||||
const { scope, name } = this;
|
||||
const [modern, fallback] = [this.modern, this.fallback].map(rules => rules.join(''));
|
||||
return [`[data-astro-transition-scope="${scope}"] { view-transition-name: ${name}; }`, this.layer(modern), fallback].join('')
|
||||
}
|
||||
|
||||
private layer(cssText: string) {
|
||||
return cssText ? `@layer astro { ${cssText} }` : '';
|
||||
}
|
||||
|
||||
private addRule(target: 'modern' | 'fallback', cssText: string) {
|
||||
this[target].push(cssText);
|
||||
}
|
||||
|
||||
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} }`)
|
||||
}
|
||||
|
||||
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 = {
|
||||
toString(): string;
|
||||
[key: string]: string[] | ((k: string) => string);
|
||||
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in a new issue