Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VT event lifecycle docs #5425

Merged
merged 17 commits into from
Nov 22, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 120 additions & 22 deletions src/content/docs/en/guides/view-transitions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -465,20 +465,108 @@ If you have code that sets up global state, this state will need to take into ac

Module scripts are only ever executed once because the browser keeps track of which modules are already loaded. For these scripts, you do not need to worry about re-execution.

### `astro:page-load`
## Lifecycle events

An event that fires at the end of page navigation, after the new page is visible to the user and blocking styles and scripts are loaded. You can listen to this event on the `document`.
The `<ViewTransition />` router fires a number events on the `document` during navigation. These events provide hooks into the lifecycle of navigation, allowing you to do things like show indicators that a new page is loading, override default behavior, and restore state as navigation is completing.

The `<ViewTransitions />` component fires this event both on initial page navigation (MPA) and any subsequent navigation, either forwards or backwards.
The navigation process involves a **preparation** phase, when new content is loaded; a **DOM swap** phase, where the old page's content is replaced by the new page's content; and a **completion** phase where scripts are executed, loading is reported as completed and clean-up work is carried out.

You can use this event to run code on every page navigation, or only once ever:
Astro's View Transition API lifecycle events in order are:
- [`astro:before-preparation`](#astrobefore-preparation)
- [`astro:after-preparation`](#astroafter-preparation)
- [`astro:before-swap`](#astrobefore-swap)
- [`astro:after-swap`](#astroafter-swap)
- [`astro:page-load`](#astropage-load)

```astro "{ once: true }"
<script>
document.addEventListener('astro:page-load', () => {
// This only runs once.
setupStuff();
}, { once: true });
:::tip
`before-` events allow you to influence and modify actions that are about to take place, and `after-` events are notifications that a phase is complete.
:::

While some actions can be triggered during any event, some tasks can only be performed during a specific event for best results, such as displaying a loading spinner before preparation or overriding animation pairs before swapping content.

### `astro:before-preparation`

<Since v="3.6.0" />

An event that fires at the beginning of the preparation phase, after navigation has started (e.g. after the user has clicked a link), but before content is loaded.

This event is used:

- To do something before loading has started, such as showing an a loading spinner.
- To alter loading, such as loading content you've defined in a template rather than from the external URL.
- To change the `direction` of the navigation (which is usually either `forward` or `backward`) for custom animation.

Here is an example of using the `astro:before-preparation` event to load a spinner before the content is loaded and stop it immediately after loading. Note that using the loader callback in this way allows asynchronous execution of code.

```js
<script is:inline>
document.addEventListener('astro:before-preparation', ev => {
const originalLoader = ev.loader;
ev.loader = async function() {
const { startSpinner } = await import('./spinner.js');
const stop = startSpinner();
await originalLoader();
stop();
};
});
</script>
```

sarah11918 marked this conversation as resolved.
Show resolved Hide resolved
### `astro:after-preparation`

<Since v="3.6.0" />

An event that fires at the end of the preparation phase, after the new page's content has been loaded and parsed into a document. This event occurs before the view transitions phase.

This example uses the `astro:before-preparation` event to start a loading indicator and the `astro:after-preparation` event to stop it:

```astro
<script is:inline>
document.addEventListener('astro:before-preparation', () => {
document.querySelector('#loading').classList.add('show');
});
document.addEventListener('astro:after-preparation', () => {
document.querySelector('#loading').classList.remove('show');
});
</script>
```
This is a simpler version of loading a spinner than the example shown above: if all of the listener's code can be executed synchronously, there is no need to hook into the loader's callback.

matthewp marked this conversation as resolved.
Show resolved Hide resolved
### `astro:before-swap`

<Since v="3.6.0" />

An event that fires before the new document (which is populated during the preparation phase) replaces the current document. This event occurs inside of the view transition, where the user is still seeing a snapshot of the old page.

This event can be used to make changes before the swap occurs. The `newDocument` property on the event represents the incoming document. Here is an example of ensuring the browser's light or dark mode preference in `localStorage` is carried over to the new page:

```astro
<script is:inline>
function setDarkMode(document) {
let theme = localStorage.darkMode ? 'dark' : 'light';
document.documentElement.dataset.theme = theme;
}

setDarkMode(document);

document.addEventListener('astro:before-swap', ev => {
// Pass the incoming document to set the theme on it
setDarkMode(ev.newDocument);
});
</script>
```

The `astro:before-swap` event can also be used to change the *implementation* of the swap. The default swap implementation diffs head content, moves __persistent__ elements from the old document to the `newDocument`, and then replaces the entire `body` with the body of the new document.

At this point of the lifecycle, you could choose to define your own swap implementation, for example to diff the entire contents of the existing document (which some other routers do):

```astro
<script is:inline>
document.addEventListener('astro:before-swap', ev => {
ev.swap = () => {
diff(document, ev.newDocument);
};
});
</script>
```

Expand All @@ -488,20 +576,30 @@ An event that fires immediately after the new page replaces the old page. You ca

This event, when listened to on the **outgoing page**, is useful to pass along and restore any state on the DOM that needs to transfer over to the new page.

For example, if you are implementing dark mode support, this event can be used to restore state across page loads:
This is the latest point in the lifecyle where it is still safe to, for example, add a dark mode class name (`<html class="dark-mode">`), though you may wish to do so in an earlier event.

```astro
The `astro:after-swap` event occurs immediately after the browser history has been updated and the scroll position has been set.
Therefore, one use of targeting this event is to override the default scroll restore for history navigation. The following example resets the horizontal and vertical scroll position to the top left corner of the page for each navigation.

```js
document.addEventListener('astro:after-swap',
() => window.scrollTo({ left: 0, top: 0, behavior: 'instant' }))
```

sarah11918 marked this conversation as resolved.
Show resolved Hide resolved
### `astro:page-load`

An event that fires at the end of page navigation, after the new page is visible to the user and blocking styles and scripts are loaded. You can listen to this event on the `document`.

The `<ViewTransitions />` component fires this event both on initial page navigation for a pre-rendered page and on any subsequent navigation, either forwards or backwards.

You can use this event to run code on every page navigation, or only once ever:

```astro "{ once: true }"
<script>
const setDarkMode = () => {
if (localStorage.darkMode) {
document.documentElement.dataset.dark = '';
}
};

// Runs on initial navigation
setDarkMode();
// Runs on view transitions navigation
document.addEventListener('astro:after-swap', setDarkMode);
document.addEventListener('astro:page-load', () => {
// This only runs once.
setupStuff();
}, { once: true });
</script>
```

Expand Down
Loading