Skip to content

Commit 21b7f1a

Browse files
authored
Revise two-phase transition explainer
Updated the two-phase transition explainer to enhance clarity and detail on cross-document navigation and proposed solutions.
1 parent d45bed7 commit 21b7f1a

File tree

1 file changed

+69
-31
lines changed

1 file changed

+69
-31
lines changed
Lines changed: 69 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,92 @@
1-
# Two-phase view transition
2-
Making cross-document view-transitions feel instant and seamless.
1+
# Two-phase cross-document navigation
2+
Making the navigation experience customizable & declarative.
33

4-
See whatwg/html#10616
4+
See whatwg/html#10616, whatwg/html#11819 and w3c/csswg-drafts#12829.
55

6-
# Current state
7-
View-transitions don’t start immediately - rendering is paused until the new state is ready.
8-
This is especially visible in cross-document view transitions, where the transition only captures the old when the new document’s response arrives, and starts when the new document is ready to render.
6+
# Overview
7+
The period between initiating a navigation (e.g. clicking a link) and consuming the content of the next page ([FCP](https://web.dev/articles/fcp)), is a sensitive moment in user experince.
8+
It is a point in time where users are very likely to notice delays, jarring moments of frozen display, and abrupt changes to presentation.
99

10-
This can create a jarring or abrupt experience.
10+
The core of this being difficult stems from a tradeoff between speed and smoothness.
11+
The new document becomes activate ASAP (in favor of speed), at the moment the headers are received.
12+
However, it cannot render until all of its render-blocking resources and elements are present (in favor of smoothness, preventing FoUCs).
1113

12-
# Current workarounds
14+
This "uncanny valley" where the old page is no longer active but the new page cannot render anything is far from being an optimal user experience, and the knobs given for developers to control it are crude and implicit.
15+
16+
# Current knobs
17+
18+
## Cross-document view transitions
19+
20+
This feature declaratively makes the navigation smoother. However, the view-transition starts very late. It captures the old state when the new page's response headers arrive, and then freezes the presentation until rendering is unblocked.
1321

1422
## Starting an animation manually on the old page
15-
This is possible, however the old page cannot delay the navigation, which means the animation is likely to be interrupted when the navigation commits.
23+
This would create an instant response to the navigation, however the animation would be interrupted as soon as the new page's headers arrive, freezing at that point. So by default this would be both abrupt and jarring.
1624

1725
## Intercepting the navigation and restarting it when the animation ends
18-
This works but is hard to achieve without slowing down the whole navigation process.
26+
This would feel smoother but slows down the whole navigation and tweaking it correctly is finicky.
1927

2028
## Timing out render-blocking
21-
This can reduce the “frozen” time, however it means the transition doesn’t end at the optimal state, and also doesn’t help with making it feel instant.
29+
This can reduce the jarring time, however it means the transition doesn’t end at the optimal state, and also doesn’t help with making it feel instant.
2230

23-
# Proposed solution
24-
The core of the problem is that view-transitions require a start and an end phase before starting, but we don’t know the end phase in advance when it is computed in an async function (e.g. in a navigation).
31+
# Two-phase transition
2532

26-
## In a nutshell
27-
Proposing to prototype a “two-phase” view-transition:
28-
Instantly start a transition to a state that can be computed quickly enough or synchronously. Call this a “preview” state.
29-
When the to-preview transition ends, transition from there to the final state.
30-
Only interrupt the first transition after a timeout, otherwise stall the navigation commit until finished.
31-
This should ideally not delay the LCP / loading experience of the new page, as the content keeps loading (and potentially prerendering) in the background.
33+
To make this part of the experience feel more seamless, developers should be able to create a "two-phase page transition".
34+
This transition starts *instantly* after navigation initiation (link click), and continues *smoothly* until the next page is ready to render.
35+
The instant part of the transition can only use information knows to the old page, which could be a skeleton of the new page or something generic of sorts.
3236

33-
## Phase 1: script-invoked preview
34-
We can create this kind of seamless/instant experience without any new CSS, and potentially without needing to fully spec it normatively by changing the behavior as follows:
35-
Calling document.startViewTransition while there is an uncommitted navigation currently works, however it might get cancelled if the navigation is committed.
36-
Instead, if setting up that preview transition’s new state is fast enough so that it is activated before commit, let the animation run its course and use the final state as the “old” state for the cross-document view-transition.
37+
To achieve that, there are 3(?) potential avenues
3738

38-
## Phase 2: declarative, using route-matching
39-
Instead of relying on carefully crafted scripts, use the proposed [declarative routing feature](https://github.com/WICG/declarative-partial-updates/blob/main/route-matching-explainer.md), and compute the preview value declaratively and synchronously by applying the style associated with the new route and using it as the intermediate state.
39+
## Heuristic-based
40+
Allow a subset of animations, e.g. ones that started after the navigation was initiated, to continue until the new page is ready to commit.
41+
This would allow instant reactions to a navigation while not creating the abrupt experience of spotting it prematurely.
4042

41-
Something like this, though perhaps the “preview” opt-in is not necessary and we can make this inferred
43+
## Low-level knobs with prerendering support
44+
Currently, deferring the commit, even for same-origin navigation, is not possible. So the browser is responsible for the handover,
45+
not allowing the developer to curate this experience.
4246

43-
@route (to: article) {
44-
.article-skeleton { display: block }
45-
}
47+
### Deferring commit
48+
Something like `navigateEvent.waitUntil(promise)` (or `defer` or some such) can let the developer tweak the handover point.
49+
This can of course also be a footgun as it's a simple way to delay navigations, however it's arguably less of a footgun than the current workarounds.
50+
51+
### Responding to prerender
52+
When prerendering takes place, a more sensible time to hand over the control to the new document is when it is ready to produce a frame (all the render-blocking resources had been discovered).
53+
However, it is not guaranteed that the destination page is prerendered, and there is no hook to know when the new page is ready to render.
54+
55+
A rather low level way to expose this is `navigateEvent.prerender()` which initiates a prerender if that hasn't happened yet, and returns a promise that resolves at that point, and compose it with the `waitUntil` method above.
56+
It is also possible to short-circuit this and somehow declare "please defer commit until prerender", which is perhaps safer than a general-purpose promise-based API.
57+
58+
## Declarative preview transitions
4659

60+
The above knobs might be very effective, but might also require expertise to get right.
61+
62+
```css
4763
@view-transition {
4864
navigation: preview;
65+
types: skeleton;
66+
}
67+
68+
:active-view-transition-types(skeleton) {
69+
/* style the transition here */
4970
}
71+
```
72+
73+
This is especially expressive together with route-matching:
5074

51-
The big advantage of doing this declaratively is that the author doesn't have to worry about "cleaning up after themselves", e.g. in the case of restoring from BFCache.
52-
Since routing is declarative, the style of the "new" route would simply not apply when restoring the "old" page from BFCache because the user is no longer navigating to it.
75+
```css
76+
@route (from: movie-list) and (to: movie-details) {
77+
@view-transition {
78+
navigation: preview;
79+
types: skeleton;
80+
}
81+
}
82+
83+
@route (movie-details) {
84+
:active-view-transition-types(skeleton) {
85+
/* style the page as a details page skeleton even if we don't have all the data */
86+
}
87+
}
88+
```
5389

90+
# Conclusion
91+
Proposing to pursue both the declarative and JS-based approach for completeness (one for ease of use, one for fine-tuning and complete control), and avoid the heuristic approach as it's a bit opaque and implicit.
5492

0 commit comments

Comments
 (0)