Open jakearchibald opened 2 years ago
Maybe I'll share a few thoughts, just from my perspective
- For the feature to work, it needs to capture the state of the DOM before and after the change.
There's an added wrinkle here that "capturing" is as either single snapshot or can be flattened into a series of non-overlapping elements (is that right?). And that the transition between those two states (before/after) can be represented as a transform between those two images.
- Capturing the 'before' state is async, as it needs to wait for the next render to read back textures.
And that the page is responsible for making sure it is largely static until that snapshot is taken. Existing animations or other UI changes need to be paused, waited, or finished.
- To cater for frameworks that batch DOM updates, updating the DOM may be async.
I do think it would be good to be more specific about the expectations for how long it should really take to update the DOM. I think there's also some expectation in the API that longer work (like network requests) that are necessary to perform the transition should probably be done before the transition even begins. Making an API async can sometimes invite people to think that it's an opportunity for them to take as long as they need, but really I think the intention with making it async is that your work is resolved within a few microtasks, perhaps.
- If the DOM change fails, the transition should not happen. An uncaught error during the DOM change indicates a failure.
This I think is perhaps a little controversial, and I would actually expect that most SPA transitions today do NOT abort on an uncaught error. I would suggest relaxing this a bit, or at least make it up to the user what should and shout not abort the transitions as far as errors.
There will be some cases where an Error indicates a complete failure, but there are others that might be completely benign. I think it's really only possible to determine whether a DOM change fails or not from the page itself, I don't think the browser has enough certainty to determine that.
- If the transition is misconfigured (for example, two elements have the same
page-transition-tag
), the transition shouldn't happen. This may be detected before the DOM change, but it shouldn't prevent the DOM change.
I think this gets more complicated with MPA where you might not have full control over the next page, but would still want something reasonable for your transition out. I think there will probably be SOME misconfigurations that do not completely abort the transition and are recoverable.
- Right now, transitions apply to the whole document, and two transitions cannot happen concurrently. Starting one transition before the other has finished causes the earlier transition to 'skip'. However, the DOM update is not skipped, as skipping a transition doesn't necessarily mean the underlying change should be prevented (think of two updates that increment a counter).
Yeah, but just highlighting that it's up to the page whether a new transition should preempt an existing DOM change or do the DOM change immediately. This gets more complicated if DOM changes are async... again encouraging people to make their DOM changes short.
--
I think the API requires that the page knows basically exactly what the page looks like before and after the transition, and just wants to add some easy animation between those states.
I think when people read this proposal, they may think that it aims to be something more than it is. If I had to describe this API, it's "if your page changes quickly from one DOM structure to another, you can add an easy animation to make that transition". It is not "we aim to provide browser support for the full view transition lifecycle."
I think where confusion creeps in is that the API shape seems to imply that it is more concerned with lifecycle than it actually is - I think people who have nuance about transitions (different styles of abort, etc), DOM changes that are long, or any uncertainty about what the page will look like after the transition, should not use this API.
There's an added wrinkle here that "capturing" is as either single snapshot or can be flattened into a series of non-overlapping elements (is that right?).
This issue isn't really dealing with the CSS side of things, it's more about how the developer interacts with the JS API. The explainer has details on the capturing, and there are further details in the spec.
- Capturing the 'before' state is async, as it needs to wait for the next render to read back textures.
And that the page is responsible for making sure it is largely static until that snapshot is taken. Existing animations or other UI changes need to be paused, waited, or finished.
Maybe? Sites don't generally delay navigations on animations finishing, so an abrupt pause of the outgoing state might be ok in many situations.
- To cater for frameworks that batch DOM updates, updating the DOM may be async.
I do think it would be good to be more specific about the expectations for how long it should really take to update the DOM. I think there's also some expectation in the API that longer work (like network requests) that are necessary to perform the transition should probably be done before the transition even begins. Making an API async can sometimes invite people to think that it's an opportunity for them to take as long as they need, but really I think the intention with making it async is that your work is resolved within a few microtasks, perhaps.
Correct. There are two parts to this solution:
- If the DOM change fails, the transition should not happen. An uncaught error during the DOM change indicates a failure.
This I think is perhaps a little controversial, and I would actually expect that most SPA transitions today do NOT abort on an uncaught error. I would suggest relaxing this a bit,
That is controversial! It would clash with other APIs that follow a similar pattern, such as the Navigation API, and the Web Locks API.
The rough steps are:
Step 2 is given to the developer, via the updateDOM
callback. It seems to me like a fundamental of programming, that an uncaught error is a signal that the operation did not complete fully and successfully in a way the developer intended. That's what throwing errors means across the rest of the platform and programming in general.
I think it would be a real mistake to take this "something went badly wrong" signal and assume "ah well it's probably ok".
or at least make it up to the user what should and shout not abort the transitions as far as errors.
That's already possible:
document.createTransition({
async updateDOM() {
try {
await framework.performDOMUpdate();
} catch (err) {
// just swallow all errors
}
},
});
That means that transitions would apply to potentially broken content, but hopefully it would be clear to the developer that they were intending that by obscuring the "it went wrong" signal.
There will be some cases where an Error indicates a complete failure, but there are others that might be completely benign. I think it's really only possible to determine whether a DOM change fails or not from the page itself, I don't think the browser has enough certainty to determine that.
Right. That's why the API leaves it to the developer to return that signal.
- If the transition is misconfigured (for example, two elements have the same
page-transition-tag
), the transition shouldn't happen. This may be detected before the DOM change, but it shouldn't prevent the DOM change.I think this gets more complicated with MPA where you might not have full control over the next page, but would still want something reasonable for your transition out. I think there will probably be SOME misconfigurations that do not completely abort the transition and are recoverable.
This issue is focusing on the SPA API. For the MPA API I still think we should fail on misconfiguration in either of the captures, but yes it's harder to confirm compatibility between the two. My current thinking is to allow a clonable object to be passed from the outgoing to incoming page, and I'd recommend developers use some sort of versioning scheme to bail the transition if they have a low confidence that the two pages can transition in a meaningful way.
I think when people read this proposal, they may think that it aims to be something more than it is. If I had to describe this API, it's "if your page changes quickly from one DOM structure to another, you can add an easy animation to make that transition". It is not "we aim to provide browser support for the full view transition lifecycle."
I think that's right. The transition 'wraps' a DOM change. But I think developers will frequently make that DOM change without the transition API (either due to a browser not-supporting the transition API, or they want to avoid it due to user preference). So, the transition API should be about transitions only. It shouldn't provide scheduling features beyond that.
I think where confusion creeps in is that the API shape seems to imply that it is more concerned with lifecycle than it actually is
Yeah, the feature is kinda tangled with the DOM change.
A navigation fails if the DOM change fails. A navigation doesn't fail if the transition fails. However, the transition 'wraps' the DOM change, since it should fail if the DOM change fails. The domUpdated
promise is an attempt to 'untangle' it a bit. I'm not sure if there's a better way.
- I think people who have nuance about transitions (different styles of abort, etc), DOM changes that are long, or any uncertainty about what the page will look like after the transition, should not use this API.
I don't think that's the right conclusion. I think the correct conclusion is: Developers shouldn't build transitions that require more certainty about the content than they have. Eg, they shouldn't create a transition that assumes both states have a header, if they're not certain both states have a header. In that case they should either build a transition that can deal with the header existing in both states, one state, or neither state, or don't try to do anything special with the header. Simpler transitions, such as a cross-fade, don't require much certainty between the two states.
"Don't act with certainty if you're not certain" seems like basic sense to me, but maybe I'm too deep in this.
I chatted the API through with @surma, and here's the feedback:
It's mostly intuitive that:
updateDOM
will always be called, even if the transition is skipped/failed beforehand.updateDOM
s will be called.ready
should reject if the transition cannot reach the about-to-start state..skipTransition()
refers to skipping the animation, it doesn't mean skipping updateDOM
.domUpdated
resolves along with updateDOM
.updateDOM
should cause the transition to fail.Stuff that was less clear:
In the case of:
const transition = document.createTransition({
async updateDOM() {
await coolFramework.setState(stuff);
}
});
const transition = document.createTransition({
async updateDOM() {
await coolFramework.setState(otherStuff);
}
});
…it wasn't clear whether the transition should be from the state when createTransition
was called, or after the change by the first updateDOM
.
Some feeling that ready
should not reject before domUpdated
fulfills, and never resolve if domUpdated
never resolves.
skipTransition()
might be better named as skipToEnd()
, skipToFinish()
, finish()
.
Most of the disagreement with the current API is around .finished
:
updateDOM
rejects.domUpdated
, and never resolve if domUpdated
never resolves.finished
resolves to.transition
.That is controversial! It would clash with other APIs that follow a similar pattern, such as the Navigation API, and the Web Locks API.
This was a misunderstanding on my part of what you meant by an Error being thrown. You mean specifically an Error being thrown in the updateDOM callback? I thought you meant any page level Error at all during a transition. My expectation is that other JS will continue to run outside the updateDOM callback. But yeah, makes sense.
The developer may wish to skip their transition if the previous transition fails to complete successfully and fully.
Is there a way to access the global list of active transitions or do you have to keep track of them yourself?
…it wasn't clear whether the transition should be from the state when createTransition was called, or after the change by the first updateDOM.
My assumption is that by default transitions queue, first come first serve, and the DOM change is uncancellable via the transition API itself?
Some feeling that ready should not reject before domUpdated fulfills, and never resolve if domUpdated never resolves.
Consider as an alternative if people aren't really paying attention to the rejected value of ready to do anything:
const transition = document.createTransition({
async updateDOM() {…}
beforeTransition() {}
});
If the only use case for observing ready
is to run additional JS animation, perhaps it feels like that work is more like "part of the transition", which side-steps having to consider when and with what to reject. Another alternative:
const transition = document.createTransition({
async updateDOM() {…}
});
transition.addEventListener('beforeTransition', () => {});
Something else should indicate whether the animation played through. This could be
What's the use case for knowing if an animation ran or not? I agree that finished is most useful as "the dom is changed and the animation is settled". Finished resolving to a value is alright.
Re-iterating some things we discussed in a private chat:
I think the confusing thing for me is the distinction between a transition and its underlying animation. Some of the APIs/promises refer to the former and some to the latter.
The transition, with its updateDOM
, always takes place. It might or might not be animated due to various reasons.
If the naming or so reflects that, I think the whole API would be a lot clearer.
Solving it might be a matter of naming bikeshedding, perhaps:
const transition = document.createTransition({ async updateDOM() { ... });
// Rejects if animation cannot be performed
transition.readyToAnimate : Promise<void>
// Rejects if the animation was skipped or not performed for some reason
transition.animationComplete : Promise<void>
// Always resolves once the animation is completed or skipped
transition.finished : Promise<void>
// Rejected only if `updateDOM` throws an exception, otherwise resolved when DOM updates are applied
transition.domUpdated : Promise<void>
// Fast forwards the animation and completes the transition immediately
transition.finish() : void
@tbondwilkinson
The developer may wish to skip their transition if the previous transition fails to complete successfully and fully.
Is there a way to access the global list of active transitions or do you have to keep track of them yourself?
No, but I think we'll need something like that in future. If we end up allowing transitions to be limited to a particular element, it creates a situation where:
…but otherwise, two transitions can happen in parallel.
Given this, the queuing system will need some thought to get right.
Some feeling that ready should not reject before domUpdated fulfills, and never resolve if domUpdated never resolves.
Consider as an alternative if people aren't really paying attention to the rejected value of ready to do anything:
const transition = document.createTransition({ async updateDOM() {…} beforeTransition() {} });
If the only use case for observing
ready
is to run additional JS animation, perhaps it feels like that work is more like "part of the transition", which side-steps having to consider when and with what to reject. Another alternative:
Yeah, I've had similar thoughts (but just called it ready
in sketches). Although… it really feels like it should be a promise, especially when you also add a callback for the "failed to become ready" state.
Something else should indicate whether the animation played through.
What's the use case for knowing if an animation ran or not? I agree that finished is most useful as "the dom is changed and the animation is settled". Finished resolving to a value is alright.
The use-cases I can think of are logging (are animations unexpectedly not-finishing?), and "I want my transition to run after the current transition, but if the current transition skips, mine should also skip".
A couple of folks said the API is confusing in parts. Maybe it can be improved. As a basis for the conversation, here are the current assumptions:
page-transition-tag
), the transition shouldn't happen. This may be detected before the DOM change, but it shouldn't prevent the DOM change.The current API:
updateDOM
- this is where the developer changes the DOM. The callback allows the feature to know when the DOM change is complete (via the returned promise) and rejections indicate failure. This callback is always called, as the DOM update is more important than the transition.domUpdated
- this exposes the result ofupdateDOM
for use in other APIs, such as the navigation API. It may fulfill even if the transition is skipped, or cannot happen due to misconfiguration.ready
- this fulfills when the transition is ready to go, so parts of the animation can be driven with JS. It rejects if the transition fails to become ready, due to skipping, misconfiguration, or a failure in the DOM change.finished
- this fulfills when the transition animates to completion. It rejects if the transition fails to fully finish, due to skipping, misconfiguration, or a failure in the DOM change.skipTransition()
- causes this transition to end abruptly, and rejectsready
andfinished
if they haven't already resolved.