-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial View Transition Support (#7511)
* Basic support * Add the fade transition * Move CSS into a separate file * Add transition name * View Transitions changeset * Replace the boolean transition with 'morph' * Update to use `transition:animate` * Use head propagation * Move CSS into a separate file * Add builtin animations and namespaced module * Misquote * Remove unused code * Add automatic prefetching to the View Transitions router * Use a data attribute for back nav animations * Use [data-astro-transition] * Add view transitions to examples * Wait on the HTML response before calling startViewTransition * Updated stuff * Update the compiler * Fix * Fallback support * Properly do fallback * Simplify the selectors * Put viewTransitions support behind a flag * Upgrade the compiler * Remove unused import * Add tests * Use an explicit import instead of types * Fix case where the click comes from within nested content * Fix linting * Add a test for the back button * Prevent glitch in fallback * Do not combine selectors * Fallback to MPA nav if there is an issue fetching * Fallback swap if there are no animations * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger <[email protected]> * Update packages/astro/components/ViewTransitions.astro Co-authored-by: Emanuele Stoppa <[email protected]> * Update packages/astro/components/ViewTransitions.astro Co-authored-by: Emanuele Stoppa <[email protected]> * Update the changeset * PR review changes * Update more based on review comments. * Update the updateDOM default * Pass in transitions options to the compiler * Update broken tests * Update .changeset/silly-garlics-live.md Co-authored-by: Sarah Rainsberger <[email protected]> * Update .changeset/silly-garlics-live.md Co-authored-by: Sarah Rainsberger <[email protected]> * Update .changeset/silly-garlics-live.md Co-authored-by: Sarah Rainsberger <[email protected]> * Update .changeset/silly-garlics-live.md Co-authored-by: Sarah Rainsberger <[email protected]> * h2 -> h4 * Upgrade to stable compiler * Remove exp redirects from sitemap * Remove usage from examples * Remove example updates --------- Co-authored-by: Sarah Rainsberger <[email protected]> Co-authored-by: Emanuele Stoppa <[email protected]>
- Loading branch information
1 parent
eafe996
commit 6a12fce
Showing
30 changed files
with
779 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
--- | ||
'astro': minor | ||
--- | ||
|
||
Built-in View Transitions Support (experimental) | ||
|
||
Astro now supports [view transitions](https://developer.chrome.com/docs/web-platform/view-transitions/) through the new `<ViewTransitions />` component and the `transition:animate` (and associated) directives. View transitions are a great fit for content-oriented sites, and we see it as the best path to get the benefits of client-side routing (smoother transitions) without sacrificing the more simple mental model of MPAs. | ||
|
||
Enable support for view transitions in Astro 2.9 by adding the experimental flag to your config: | ||
|
||
```js | ||
import { defineConfig } from 'astro/config'; | ||
|
||
export default defineConfig({ | ||
experimental: { | ||
viewTransitions: true, | ||
}, | ||
}) | ||
``` | ||
|
||
This enables you to use the new APIs added. | ||
|
||
#### <ViewTransitions /> | ||
|
||
This is a component which acts as the *router* for transitions between pages. Add it to the `<head>` section of each individual page where transitions should occur *in the client* as you navigate away to another page, instead of causing a full page browser refresh. To enable support throughout your entire app, add the component in some common layout or component that targets the `<head>` of every page. | ||
|
||
__CommonHead.astro__ | ||
|
||
```astro | ||
--- | ||
import { ViewTransitions } from 'astro:transitions'; | ||
--- | ||
<meta charset="utf-8"> | ||
<title>{Astro.props.title}</title> | ||
<ViewTransitions /> | ||
``` | ||
|
||
With only this change, your app will now route completely in-client. You can then add transitions to individual elements using the `transition:animate` directive. | ||
|
||
#### Animations | ||
|
||
Add `transition:animate` to any element to use Astro's built-in animations. | ||
|
||
```astro | ||
<header transition:animate="slide"> | ||
``` | ||
|
||
In the above, Astro's `slide` animation will cause the `<header>` element to slide out to the left, and then slide in from the right when you navigate away from the page. | ||
|
||
You can also customize these animations using any CSS animation properties, for example, by specifying a duration: | ||
|
||
```astro | ||
--- | ||
import { slide } from 'astro:transition'; | ||
--- | ||
<header transition:animate={slide({ duration: 200 })}> | ||
``` | ||
|
||
#### Continue learning | ||
|
||
Check out the [client-side routing docs](https://docs.astro.build/en/guides/client-side-routing/) to learn more. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
--- | ||
--- | ||
interface Props { | ||
title: string; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
--- | ||
type Fallback = 'none' | 'animate' | 'swap'; | ||
export interface Props { | ||
fallback?: Fallback; | ||
} | ||
const { fallback = 'animate' } = Astro.props as Props; | ||
--- | ||
<meta name="astro-view-transitions-enabled" content="true"> | ||
<meta name="astro-view-transitions-fallback" content={fallback}> | ||
<script> | ||
type Fallback = 'none' | 'animate' | 'swap'; | ||
type Direction = 'forward' | 'back'; | ||
|
||
// The History API does not tell you if navigation is forward or back, so | ||
// you can figure it using an index. On pushState the index is incremented so you | ||
// can use that to determine popstate if going forward or back. | ||
let currentHistoryIndex = history.state?.index || 0; | ||
if(!history.state) { | ||
history.replaceState({index: currentHistoryIndex}, document.title); | ||
} | ||
|
||
const supportsViewTransitions = !!document.startViewTransition; | ||
const transitionEnabledOnThisPage = () => !!document.querySelector('[name="astro-view-transitions-enabled"]'); | ||
|
||
async function getHTML(href: string) { | ||
const res = await fetch(href) | ||
const html = await res.text(); | ||
return { ok: res.ok, html }; | ||
} | ||
|
||
function getFallback(): Fallback { | ||
const el = document.querySelector('[name="astro-view-transitions-fallback"]'); | ||
if(el) { | ||
return el.getAttribute('content') as Fallback; | ||
} | ||
return 'animate'; | ||
} | ||
|
||
const parser = new DOMParser(); | ||
|
||
async function updateDOM(dir: Direction, html: string, fallback?: Fallback) { | ||
const doc = parser.parseFromString(html, 'text/html'); | ||
doc.documentElement.dataset.astroTransition = dir; | ||
const swap = () => document.documentElement.replaceWith(doc.documentElement); | ||
|
||
if(fallback === 'animate') { | ||
let isAnimating = false; | ||
addEventListener('animationstart', () => isAnimating = true, { once: true }); | ||
|
||
// Trigger the animations | ||
document.documentElement.dataset.astroTransitionFallback = 'old'; | ||
doc.documentElement.dataset.astroTransitionFallback = 'new'; | ||
// If there are any animations, want for the animationend event. | ||
addEventListener('animationend', swap, { 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. | ||
setTimeout(() => !isAnimating && swap()); | ||
} else { | ||
swap(); | ||
} | ||
} | ||
|
||
async function navigate(dir: Direction, href: string) { | ||
let finished: Promise<void>; | ||
const { html, ok } = await getHTML(href); | ||
// If there is a problem fetching the new page, just do an MPA navigation to it. | ||
if(!ok) { | ||
location.href = href; | ||
return; | ||
} | ||
if(supportsViewTransitions) { | ||
finished = document.startViewTransition(() => updateDOM(dir, html)).finished; | ||
} else { | ||
finished = updateDOM(dir, html, getFallback()); | ||
} | ||
try { | ||
await finished; | ||
} finally { | ||
document.documentElement.removeAttribute('data-astro-transition'); | ||
} | ||
} | ||
|
||
// Prefetching | ||
function maybePrefetch(pathname: string) { | ||
if(document.querySelector(`link[rel=prefetch][href="${pathname}"]`)) return; | ||
if(navigator.connection){ | ||
let conn = navigator.connection; | ||
if(conn.saveData || /(2|3)g/.test(conn.effectiveType || '')) return; | ||
} | ||
let link = document.createElement('link'); | ||
link.setAttribute('rel', 'prefetch'); | ||
link.setAttribute('href', pathname); | ||
document.head.append(link); | ||
} | ||
|
||
if(supportsViewTransitions || getFallback() !== 'none') { | ||
document.addEventListener('click', (ev) => { | ||
let link = ev.target; | ||
if(link instanceof Element && link.tagName !== 'A') { | ||
link = link.closest('a'); | ||
} | ||
// This check verifies that the click is happening on an anchor | ||
// that is going to another page within the same origin. Basically it determines | ||
// same-origin navigation, but omits special key combos for new tabs, etc. | ||
if (link && | ||
link instanceof HTMLAnchorElement && | ||
link.href && | ||
(!link.target || link.target === '_self') && | ||
link.origin === location.origin && | ||
ev.button === 0 && // left clicks only | ||
!ev.metaKey && // new tab (mac) | ||
!ev.ctrlKey && // new tab (windows) | ||
!ev.altKey && // download | ||
!ev.shiftKey && | ||
!ev.defaultPrevented && | ||
transitionEnabledOnThisPage() | ||
) { | ||
ev.preventDefault(); | ||
navigate('forward', link.href); | ||
currentHistoryIndex++; | ||
history.pushState({index: currentHistoryIndex}, '', link.href); | ||
} | ||
}); | ||
window.addEventListener('popstate', () => { | ||
if(!transitionEnabledOnThisPage()) return; | ||
const nextIndex = history.state?.index ?? (currentHistoryIndex + 1); | ||
const direction: Direction = nextIndex > currentHistoryIndex ? 'forward' : 'back'; | ||
navigate(direction, location.href); | ||
currentHistoryIndex = nextIndex; | ||
}); | ||
|
||
['mouseenter', 'touchstart', 'focus'].forEach(evName => { | ||
document.addEventListener(evName, ev => { | ||
if(ev.target instanceof HTMLAnchorElement) { | ||
let el = ev.target; | ||
if(el.origin === location.origin && el.pathname !== location.pathname && transitionEnabledOnThisPage()) { | ||
maybePrefetch(el.pathname); | ||
} | ||
} | ||
}, { passive: true, capture: true }); | ||
}); | ||
} | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export { default as Code } from './Code.astro'; | ||
export { default as Debug } from './Debug.astro'; | ||
export { default as ViewTransitions } from './ViewTransitions.astro'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
@keyframes astroFadeInOut { | ||
from { | ||
opacity: 1; | ||
} | ||
to { | ||
opacity: 0; | ||
} | ||
} | ||
|
||
@keyframes astroFadeIn { | ||
from { opacity: 0; } | ||
} | ||
|
||
@keyframes astroFadeOut { | ||
to { opacity: 0; } | ||
} | ||
|
||
@keyframes astroSlideFromRight { | ||
from { transform: translateX(100%); } | ||
} | ||
|
||
@keyframes astroSlideFromLeft { | ||
from { transform: translateX(-100%); } | ||
} | ||
|
||
@keyframes astroSlideToRight { | ||
to { transform: translateX(100%); } | ||
} | ||
|
||
@keyframes astroSlideToLeft { | ||
to { transform: translateX(-100%); } | ||
} |
8 changes: 8 additions & 0 deletions
8
packages/astro/e2e/fixtures/view-transitions/astro.config.mjs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { defineConfig } from 'astro/config'; | ||
|
||
// https://astro.build/config | ||
export default defineConfig({ | ||
experimental: { | ||
viewTransitions: true, | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"name": "@e2e/view-transitions", | ||
"version": "0.0.0", | ||
"private": true, | ||
"dependencies": { | ||
"astro": "workspace:*" | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
packages/astro/e2e/fixtures/view-transitions/src/components/Layout.astro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
--- | ||
import { ViewTransitions } from 'astro:transitions'; | ||
--- | ||
<html> | ||
<head> | ||
<title>Testing</title> | ||
<ViewTransitions /> | ||
</head> | ||
<body> | ||
<header transition:animate="morph"> | ||
<h1>testing</h1> | ||
</header> | ||
<main transition:animate="slide"> | ||
<slot /> | ||
</main> | ||
</body> | ||
</html> |
12 changes: 12 additions & 0 deletions
12
packages/astro/e2e/fixtures/view-transitions/src/pages/four.astro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
--- | ||
import Layout from '../components/Layout.astro'; | ||
--- | ||
<Layout> | ||
<p id="four">Page 4</p> | ||
<a id="click-one" href="/one"> | ||
<div> | ||
Nested | ||
<span>go to 1</span> | ||
</div> | ||
</a> | ||
</Layout> |
8 changes: 8 additions & 0 deletions
8
packages/astro/e2e/fixtures/view-transitions/src/pages/one.astro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
--- | ||
import Layout from '../components/Layout.astro'; | ||
--- | ||
<Layout> | ||
<p id="one">Page 1</p> | ||
<a id="click-two" href="/two">go to 2</a> | ||
<a id="click-three" href="/three">go to 3</a> | ||
</Layout> |
11 changes: 11 additions & 0 deletions
11
packages/astro/e2e/fixtures/view-transitions/src/pages/three.astro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<html> | ||
<head> | ||
<title>Page 3</title> | ||
</head> | ||
<body> | ||
<main> | ||
<p id="three">Page 3</p> | ||
<a id="click-two" href="/two">go to 2</a> | ||
</main> | ||
</body> | ||
</html> |
6 changes: 6 additions & 0 deletions
6
packages/astro/e2e/fixtures/view-transitions/src/pages/two.astro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
import Layout from '../components/Layout.astro'; | ||
--- | ||
<Layout> | ||
<p id="two">Page 2</p> | ||
</Layout> |
Oops, something went wrong.