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 6 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
137 changes: 115 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,109 @@ 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 to you do things like showing indicators that a new page is loading, override default behavior, and restore state as navigation is completing.
matthewp marked this conversation as resolved.
Show resolved Hide resolved

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 the new page is loaded, scripts are executed and clean-up work is carried out.
matthewp marked this conversation as resolved.
Show resolved Hide resolved

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.
matthewp marked this conversation as resolved.
Show resolved Hide resolved

```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>
```

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

<Since v="3.6.0" />

An event that fires befores the new document, 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.
sarah11918 marked this conversation as resolved.
Show resolved Hide resolved

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):
sarah11918 marked this conversation as resolved.
Show resolved Hide resolved

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

Expand All @@ -488,20 +577,24 @@ 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
As the `astro:after-swap` event occurs immediately before the view transition ends, it's a good time to stop any loading indicators that might be running.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"occurs immediately before the view transition ends" could be misleading: Surely it is near the end of the navigation, but "astro:after-swap" takes place immediately before the start of the animation (for the simulation immediately before the start of the "new" part). In any case, the loading indicator is already recorded on the screenshot and will not disappear so quickly, depending on the animation. A better place to remove it would be astro:after-preparation because the photo has not yet been taken :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm hearing two different comments here:

  1. "immediately before the view transition ends" isn't exactly right. What's a better, concise, way to describe it?
  2. Spinner has come back to haunt us. This now is the only real example (that's not, it's still safe to do this) example for this section. Can you suggest (code sample even better, but just in words like above is fine) what a good thing to happen here is? So that this section can have a proper example of its own that makes sense and is a realistic use case? Note: it's also OK to say that "there's very little that might target this event specifically, but it does provide you..."

matthewp marked this conversation as resolved.
Show resolved Hide resolved

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