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

Add explainer for cross-document navigations #208

Merged
merged 24 commits into from May 29, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
114 changes: 114 additions & 0 deletions cross-doc-explainer.md
@@ -0,0 +1,114 @@
# Contents

# Introduction

See [the main explainer](explainer.md) for detailed explanation about view transitions.
This explainer extends the feature of same-document ("SPA") transitions to also include
cross-document transitions ("MPA").

While same-document transitions are useful in themselves and an important stepping stone, allowing
cross-document transitions would unlock a capability that so far has been available only within the
same document.

# Goals

## Compatible with same-document transitions

The vast majority of work for cross-document transitions is based on same-document transitions.
This allows developers to use the same techniques for both, and to avoid big refactors if
changing architectures.

## Declarative & Customizable

Cross-document transitions should work by default without JavaScript intervention, and should have
the right JavaScript knobs for when the defaults are not enough.

# How it works

## In a nutshell
Documents declare that they support same-origin cross-document transitions, using a
[meta tag](#opt-in). When a navigation between two such documents takes place (and has no
cross-origin redirects in the middle), the state of the old document is captured, in the same
manner as same-origin transitions. When the new document is ready for the transition (i.e., when all render-blocking resources are ready), the new state is captured,
noamr marked this conversation as resolved.
Show resolved Hide resolved
noamr marked this conversation as resolved.
Show resolved Hide resolved
and the transition continues as if it was a same-document transition, with the appropriate pseudo-elements.

The new document can customize the style of the animation using the same techniques available for same-document transitions.

Both documents can interrupt the transition or customize it using JavaScript in different phases.

## Same-origin

Cross-document view transitions are only enabled for same-origin navigations without a
[cross-origin redirect](https://html.spec.whatwg.org/#unloading-documents:was-created-via-cross-origin-redirects).
In the future we could examine exposing them in some way to same-site or cross-origin navigations.

## Opt-in

To enable cross-document transitions, both the old and new document need to add a meta tag. This tag
indicates that the author wants to enable transitions for same-origin navigations to/from this
Document.


### Issues

1. The meta tag can be used to opt-in to other navigation types going forward: same-document, same-site, etc.

1. Using meta-tags prevents the declaration being controlled by media queries, which feels important for `prefers-reduced-motion`.
noamr marked this conversation as resolved.
Show resolved Hide resolved

## Lifecycle

![Lifecycle chart for cross-document transitions](media/mpa-chart.svg)

### Capturing the old state

The old state is captured right before the old document is hidden and a new one is shown.
In the HTML spec this is defined [here](https://html.spec.whatwg.org/#populating-a-session-history-entry:loading-a-document).
noamr marked this conversation as resolved.
Show resolved Hide resolved
This can either happen during normal navigations, when the new document is about to be created,
in BFCache navigations, or when activating a prerendered document.
noamr marked this conversation as resolved.
Show resolved Hide resolved

Before creating the new document (or activating a cached/prerendered one), the UA would update the rendering and snapshot the old document, in the same manner a document is snapshotted for a same-document navigation.
noamr marked this conversation as resolved.
Show resolved Hide resolved

Note that currently there are no planned new events for the exit transition.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
Note that currently there are no planned new events for the exit transition.
Note that currently there are no planned new events for notifying before the old Document is snapshotted.

"Exit transition" was commonly used to refer to the animation done on an element which is leaving the screen. Just to avoid the confusion there.

The developer can use existing events like `navigate` or `click` to customize the
exit transition at moments that should be late enough.
noamr marked this conversation as resolved.
Show resolved Hide resolved

Copy link
Collaborator

Choose a reason for hiding this comment

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

Do you mind adding an example of this? The most common one would be naming one of the elements in the list whose details page the user is navigating to. Something like:

addeventlistener("navigate", (event) => {
  if (event.url == "item1)
    item1.viewTransitionName = "item";
});

#### Issues

1. Should we enable skipping the transition on exit? This could be done on`pagehide`.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is pagehide guaranteed to be before the new Document is created? It would be awkward for the old Document to skip the transition in this event if the new Document has already accessed it via document.activeTransition?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes it's guaranteed!


### Capturing the new state

The new state is captured at the first [rendering opportunity](https://html.spec.whatwg.org/#rendering-opportunity)
of the new document. This allows the new document to use the
[render-blocking mechanism](https://html.spec.whatwg.org/#render-blocking-mechanism) as a way to
delay the transition.

For most cases, opting in via the meta tag, styling with the pseudo-elements, and delaying the transition using the
[render-blocking mechanism](https://html.spec.whatwg.org/#render-blocking-mechanism) should be sufficient. But there are certain cases where further customization is desired, for example:

* Abort the transition if certain conditions apply.

* Modify the pseudo-element style based on certain conditions, e.g. apply transition
names only to images that are already loaded.

* Prepare the exit transition right before capture.
noamr marked this conversation as resolved.
Show resolved Hide resolved

There are several options as to how to enable this:

1. Fire an event right before the new document capture. This would be either right
noamr marked this conversation as resolved.
Show resolved Hide resolved
before the first `requestAnimationFrame` callback, or after `pageshow` in the reactivation case. Potential name: `reveal`. Note that in the latter case, it's too late to delay the transition via the render-blocking mechanism. This event doesn't
noamr marked this conversation as resolved.
Show resolved Hide resolved
have to be specific to transitions.

1. Expose something like a `document.currentRevealTransition` or so that the script can
noamr marked this conversation as resolved.
Show resolved Hide resolved
query or skip. We might also want to enable delaying the transition with a promise,
noamr marked this conversation as resolved.
Show resolved Hide resolved
the same way that's available for same-document transitions.

### Issues

1. Customizing which resources are render-blocking in `reveal` requires it to be dispatched before parsing `body`, or explicitly allow render-blocking resources to be added until this event is dispatched.

1. We'd likely need an API for the developer to control how much Document needs to be fetched/parsed before the transition starts, or delay the capture with a promise.
noamr marked this conversation as resolved.
Show resolved Hide resolved

1. The browser defers painting the new Document until all render-blocked resources have been fetched or timed out. Do we need an explicit hook for when this is done or could the developer rely on existing `load` events to detect this? This would allow authors to add viewTransitionNames based on what the new Document's first paint would look like.
noamr marked this conversation as resolved.
Show resolved Hide resolved

1. Should we allow access to the transition once its started? Perhaps access to a [ViewTransition](https://drafts.csswg.org/css-view-transitions-1/#viewtransition) object or similar?.
noamr marked this conversation as resolved.
Show resolved Hide resolved
112 changes: 2 additions & 110 deletions explainer.md
Expand Up @@ -11,7 +11,6 @@
1. [Transitioning elements don't need to exist in both states](#transitioning-elements-dont-need-to-exist-in-both-states).
1. [Customizing the transition based on the type of navigation](#customizing-the-transition-based-on-the-type-of-navigation) - e.g. creating 'reverse' transitions for 'back' traversals.
1. [Animating with JavaScript](#animating-with-javascript) - because some transitions aren't possible with CSS alone.
1. [Cross-document same-origin transitions](#cross-document-same-origin-transitions) - MPA page transitions.
1. [Compatibility with existing developer tooling](#compatibility-with-existing-developer-tooling).
1. [Compatibility with frameworks](#compatibility-with-frameworks).
1. [Error handling](#error-handling) - Ensuring DOM changes don't get lost, or stuck.
Expand Down Expand Up @@ -54,7 +53,7 @@ and [Windows](https://docs.microsoft.com/en-us/windows/apps/design/motion/page-t

# MPA vs SPA solutions
noamr marked this conversation as resolved.
Show resolved Hide resolved

The [current spec](https://drafts.csswg.org/css-view-element-transitions-1/) and experimental implementation in Chrome (behind the `chrome://flags/#document-transition` flag) focuses on SPA transitions. However, the model has also been designed to work with cross-document navigations. The specifics for cross-document navigations are covered [later in this document](#cross-document-same-origin-transitions).
The [current spec](https://drafts.csswg.org/css-view-element-transitions-1/) and experimental implementation in Chrome (behind the `chrome://flags/#document-transition` flag) focuses on SPA transitions. However, the model has also been designed to work with cross-document navigations. The specifics for cross-document navigations are covered [here](cross-doc-explainer.md).

This doesn't mean we consider the MPA solution less important. In fact, [developers have made it clear that it's more important](https://twitter.com/jaffathecake/status/1405573749911560196). We have focused on SPAs due to the ease of prototyping, so those APIs have had more development. However, the overall model has been designed to work for MPAs, with a slightly different API around it.

Expand Down Expand Up @@ -351,114 +350,7 @@ https://user-images.githubusercontent.com/93594/184120371-678f58b3-d1f9-465b-978

# Cross-document same-origin transitions

This section outlines the navigation specific aspects of the ViewTransition API. The rendering model for generating snapshots and displaying them using a tree of targetable pseudo-elements is the same for both SPA/MPA.

## Declarative opt-in to transitions

The first step is to add a new meta tag to the old and new Documents. This tag indicates that the author wants to enable transitions for same-origin navigations to/from this Document.

```html
<meta name="view-transition" content="same-origin">
```

The above is equivalent to the browser implicitly executing the following script in the SPA API:

```js
document.startViewTransition(() => updateDOMToNewPage());
```

This results in a cross-fade between the 2 Documents from the default CSS set up by the browser. The transition executes only if this tag is present on both the old and new Documents. The tag must also be added before the body element is parsed.

The motivation for a declarative opt-in, instead of a script event, is:

* Enabling authors to define transitions with no script. If the transition doesn't need to be customized based on the old/new URL, it can be defined completely in CSS.

* Avoiding undue latency in the critical path for browser initiated navigations like back/forward. We want to avoid dispatch of a script event for each of these navigations.

Issue: The meta tag can be used to opt-in to other navigation types going forward: same-document, same-site, etc.

Issue: This prevents the declaration being controlled by media queries, which feels important for `prefers-reduced-motion`.

## Script events

Script can be used to customize a transition based on the URL of the old/new Document; or the current state of the Document when the transition is initiated. The Document could've been updated since first lold from user interaction.

### Script on old document

```js
document.addEventListener("crossdocumentviewtransitionoldcapture", (event) => {
// Cancel the transition (based on new URL) if needed.
if (shouldNotTransition(event.toURL)) {
event.preventDefault();
return;
}

// Set up names on elements based on the new URL.
if (shouldTagThumbnail(event.toURL)) {
thumbnail.style.viewTransitionName = "full-embed";
}

// Add opaque contextual information to share with the new Document.
// This must be [serializable object](https://developer.mozilla.org/en-US/docs/Glossary/Serializable_object).
event.setInfo(createTransitionInfo(event.toURL));
});
```

### Script on new document

```js
// This event must be registered before the `body` element is parsed.
document.addEventListener("crossdocumentviewtransition", (event) => {
// Cancel the transition (based on old URL) if needed.
if (shouldNotTransition(event.fromURL)) {
event.preventDefault();
return;
}

// The `ViewTransitionNavigation` object associated with this transition.
const transition = event.transition;

// Retrieve the context provided by the old Document.
const info = event.info;

// Add render-blocking resources to delay the first paint and transition
// start. This can be customized based on the old Document state when the
// transition was initiated.
markRenderBlockingResources(info);

// The `ready` promise resolves when the pseudo-elements have been generated
// and can be used to customize animations via script.
transition.ready.then(() => {
document.documentElement.animate(...,
{
// Specify which pseudo-element to animate
pseudoElement: "::view-transition-new(root)",
}
);

// Remove viewTransitionNames tied to this transition.
thumbnail.style.viewTransitionName = "none";
});

// The `finished` promise resolves when all animations for the transition are
// finished or cancelled and the pseudo-elements have been removed.
transition.finished.then(() => { ... });
});
```

This provides the same scripting points as the SPA API, allowing developers to set class names to tailor the animation to a particular type of navigation.

Issue: Event names are verbose. Bikeshedding needed.

Issue: Do we need better timing for `crossdocumentviewtransition` event? Especially for Documents restored from BFCache.

Issue: Customizing which resources are render-blocking in `crossdocumentviewtransition` requires it to be dispatched before parsing `body`, or explicitly allow render-blocking resources to be added until this event is dispatched.

Issue: We'd likely need an API for the developer to control how much Document needs to be fetched/parsed before the transition starts.

Issue: The browser defers painting the new Document until all render-blocked resources have been fetched or timed out. Do we need an explicit hook for when this is done or could the developer rely on existing `load` events to detect this? This would allow authors to add viewTransitionNames based on what the new Document's first paint would look like.

Issue: Since `crossdocumentviewtransitionoldcapture` is dispatched after redirects and only if the final URL is same-origin, it allows the current Document to know whether the navigation eventually ended up on a cross-origin page. This likely doesn't matter since the site could know this after the navigation anyway but knowing on the current page before the navigation commits is new.
This section has moved to [its own explainer](cross-doc-explainer.md).

# Compatibility with existing developer tooling

Expand Down
1 change: 1 addition & 0 deletions media/mpa-chart.svg
noamr marked this conversation as resolved.
Show resolved Hide resolved
noamr marked this conversation as resolved.
Show resolved Hide resolved
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.