Closed khushalsagar closed 1 year ago
Hey folks, we just updated the explainer. It should give a better overview of the feature now https://github.com/WICG/shared-element-transitions/blob/main/explainer.md.
The previous version was written before we had a spec, so included some lower-level detail which is better left to the spec.
Hi Jake,
We looked at this during the plenary today. We generally recognize the importance of solving these very common use cases and understand that to cover enough of them, the solution would need a certain degree of complexity. However, we did have some trouble wrapping our heads around exactly how this works and how the different pieces fit together. While it is very clear that a lot of effort has been put into this explainer, we also feel that some parts assume certain knowledge about the reader. We would like to see more examples of both code and output, as well as some simpler examples that may not look nice, but illustrate how the API works. A few hello worlds of sorts 😊 Or perhaps it may help to pick two of these examples and provide the end to end code for them somewhere?
Does the developer guide provide that kind of introduction? https://developer.chrome.com/blog/shared-element-transitions-for-spas/ - each of the demos has a video, but also a link to a super simple example. There's also a 10 min video on the feature if that works better https://www.youtube.com/watch?v=JCJUPJ_zDQ4.
The difference between the developer document and the explainer, is the explainer uses the latest API (which hasn't landed in Chrome yet), and contains a few more implementation details.
Yes! At least for me this was a lot clearer. Is there anything similar about MPAs?
What are the next steps here? I could merge the more-gradual-introduction from https://developer.chrome.com/blog/shared-element-transitions-for-spas/ into the explainer.
The next step is that we revisit this in another call. I see it now has a milestone of 2022-08-22-week, which means the intent is to revisit it next week.
I wonder if part of the reason it was so hard to wrap our heads around this was that the explainer was already very long, so it was hard to know what to focus on when reviewing it during the call. This is a complex feature, so it does need a lot of, well, explaining, but I wonder if it might be easier to digest if organized hierarchically, breadth-first. Also, despite the length, we felt there were certain pieces of the puzzle missing, e.g.:
const transition = new SameDocumentTransition();
await transition.prepare(async () => {
await updateTheDOMSomehow();
});
that is just repeated throughout the explainer. It took until the link you posted above for me to finally understand how this works: You start the transition by creating a new SameDocumentTransition
instance, then make the changes to create this transition in the transition.prepare()
callback.
Hope this helps a bit!
While writing this, I had some thoughts about the actual API: It would be nice if there was a way to use SET without the extensive refactoring of having to put the DOM manipulation code in the transition.prepare()
callback, but something that is easier to make work with feature detection too (e.g. some way to signal the start and end of the transition, while DOM manipulation code remains where it was?). As an author, I wouldn't even know how to use transition.prepare()
when using a framework that does the DOM manipulation for me.
I also just noticed that this doesn't support MPAs yet, but that's an API sketch. Since that's a pretty central use case, I'll make sure we spend call time reviewing that sketch too, and not just the SPA part of the API.
This is great feedback. What day & time are folks likely to look at this? As in, assuming I'm not working the weekend (which I plan not to), how long do I have to bash the explainer into shape?
It would be nice if there was a way to use SET without the extensive refactoring of having to put the DOM manipulation code in the
transition.prepare()
callback, but something that is easier to make work with feature detection too (e.g. some way to signal the start and end of the transition, while DOM manipulation code remains where it was?)
The browser needs to capture the 'before' state, which is async. But yeah:
const transition = new SameDocumentTransition();
await transition.prepare(async () => {
await updateTheDOMSomehow();
});
…could have an alternative API like:
const transition = new SameDocumentTransition();
await transition.captureBeforeState();
await updateTheDOMSomehow();
transition.domChanged();
The main difference between the two is if updateTheDOMSomehow
throws. With the split version of the API, domChanged
won't be called, and now rendering is frozen until some kind of timeout.
With the callback model, it's more likely that we catch the error, and know to abandon the transition. WebLocks use a similar pattern for this reason.
As an author, I wouldn't even know how to use
transition.prepare()
when using a framework that does the DOM manipulation for me.
Depends on the framework. I created a React hook to do it, and it's what https://http203-playlist.netlify.app/ uses. React has useLayoutEffect
for hearing about DOM changes before the next render.
Our current schedule is to look at this again on 8/22 @18:00CET, but we can easily defer a week or two if you need more time.
I'm up for the challenge. I'll confirm here when I've updated the explainer.
Done! https://github.com/WICG/shared-element-transitions/blob/main/explainer.md
Hopefully the explainer now starts off at a high level, then goes on to explore some of the details. For additional details, the spec may be useful https://tabatkins.github.io/specs/css-shared-element-transitions/.
If it still isn't good, and you need to bail on it, there's always https://github.com/w3ctag/design-reviews/issues/761 to review instead 😄
Thank you for working on the explainer, the new one is vastly improved and much easier to follow! 👍🏼
It would be nice if there was a way to use SET without the extensive refactoring of having to put the DOM manipulation code in the
transition.prepare()
callback, but something that is easier to make work with feature detection too (e.g. some way to signal the start and end of the transition, while DOM manipulation code remains where it was?)The browser needs to capture the 'before' state, which is async. But yeah:
const transition = new SameDocumentTransition(); await transition.prepare(async () => { await updateTheDOMSomehow(); });
…could have an alternative API like:
const transition = new SameDocumentTransition(); await transition.captureBeforeState(); await updateTheDOMSomehow(); transition.domChanged();
The main difference between the two is if
updateTheDOMSomehow
throws. With the split version of the API,domChanged
won't be called, and now rendering is frozen until some kind of timeout.With the callback model, it's more likely that we catch the error, and know to abandon the transition. WebLocks use a similar pattern for this reason.
I understand, but the callback model makes this very annoying to use w/ progressive enhancement, and the Web Platform in general is trying to move away from callback-based APIs.
This is exactly the kind of API that would benefit from progressive enhancement, since if it's not available, you just don't get a transition, no big deal. It should be easy to write code that works this way. And lack of this feature is not the only reason people might want to branch and only have the transitions some of the time. E.g. the prefers-reduced-motion
media query, or a setting to turn off animations are also valid reasons to not have transitions. Branching is still possible with this API, but very cumbersome and boilerplatey:
if (animationsEnabled && globalThis.SameDocumentTransition) {
const transition = new SameDocumentTransition();
await transition.prepare(async () => {
await updateTheDOMSomehow();
});
}
else {
await updateTheDOMSomehow();
}
whereas with your alternative API sketch:
const transition = animationsEnabled && globalThis.SameDocumentTransition ? new SameDocumentTransition() : null;
await transition?.captureBeforeState();
await updateTheDOMSomehow();
transition?.domChanged();
Having the rendering be frozen until a timeout could not be such a dealbreaker if the timeout is explicitly specified when creating the transition? After all, DOM manipulation is not a lengthy process (compared to e.g. network requests), so you'd likely need a very small timeout, smaller than the threshold of a lag that is perceptible by humans (60ms). You could also take a signal to abort the operation, which is good practice anyway.
Note that the current API can still cause rendering to freeze, if you await a promise that never resolves.
As an author, I wouldn't even know how to use
transition.prepare()
when using a framework that does the DOM manipulation for me.Depends on the framework. I created a React hook to do it, and it's what http203-playlist.netlify.app uses. React has
useLayoutEffect
for hearing about DOM changes before the next render.
I’m sure it's possible with every framework, but it's yet another obstacle for authors to figure out which advanced framework feature would do this. E.g. off the top of my head I cannot think of how to reliably do this with Vue, at least not without heuristics and hacks.
I understand, but the callback model makes this very annoying to use w/ progressive enhancement Branching is still possible with this API, but very cumbersome and boilerplatey:
In the simple case, we're talking:
function spaNavigate(data) {
// Fallback for browsers that don't support this API:
if (!self.SameDocumentTransition || !animationsEnabled) {
updateTheDOMSomehow(data);
return;
}
const transition = new SameDocumentTransition();
transition.prepare(() => updateTheDOMSomehow(data));
}
Vs:
async function spaNavigate(data) {
const transition =
animationsEnabled && self.SameDocumentTransition
? new SameDocumentTransition()
: null;
await transition?.captureBeforeState();
await updateTheDOMSomehow();
transition?.domChanged();
}
(both examples formatted with https://prettier.io/)
I'm not sure the latter is less cumbersome. Every time you interact with transition?
, you need to think about the 'null' case too.
Once you start adding more transition-specific script:
thumbnail.onclick = () => {
if (!self.SameDocumentTransition || !animationsEnabled) {
updateTheDOMSomehow(data);
return;
}
const transition = new SameDocumentTransition();
thumbnail.style.pageTransitionTag = "full-embed";
transition.prepare(() => {
thumbnail.style.pageTransitionTag = "";
updateTheDOMSomehow();
});
};
Vs:
thumbnail.onclick = async () => {
const transition =
animationsEnabled && self.SameDocumentTransition
? new SameDocumentTransition()
: null;
thumbnail.style.pageTransitionTag = 'full-embed';
await transition?.captureBeforeState();
thumbnail.style.pageTransitionTag = '';
updateTheDOMSomehow();
transition?.domChanged();
};
Again, this doesn't seem particularly better, and now you're running steps that are for transitions, even in cases where you're not actually wanting a transition. In this case it's a no-op, but as the author and the reader, you need to think that through. Or, alternatively, avoid those steps if it isn't a transition:
thumbnail.onclick = async () => {
const transition =
animationsEnabled && self.SameDocumentTransition
? new SameDocumentTransition()
: null;
if (transition) {
thumbnail.style.pageTransitionTag = 'full-embed';
}
await transition?.captureBeforeState();
if (transition) {
thumbnail.style.pageTransitionTag = '';
}
updateTheDOMSomehow();
transition?.domChanged();
};
And now this is looking much more complicated than the first example. Things get worse still when you're wanting to also control parts of the transition with script. And still, there's the timeout issue if the DOM change throws.
and the Web Platform in general is trying to move away from callback-based APIs.
This is news to me, and I'm not sure it's true. Cases where an API wants to know the async result of a developer action are commonly callbacks.
Some modern examples:
flush
callback.handler
callback in the navigation API.These kinds of cases don't really make sense as promises, because it isn't purely waiting for a response from the API, it's also providing a response to the API. These kinds of cases don't really make sense as events either, since they only really have one listener, and the response may be async.
Having the rendering be frozen until a timeout could not be such a dealbreaker if the timeout is explicitly specified when creating the transition? After all, DOM manipulation is not a lengthy process (compared to e.g. network requests), so you'd likely need a very small timeout, smaller than the threshold of a lag that is perceptible by humans (60ms)
That kind of lag is acceptable when it comes to reaction to a click, but pausing animations for 60ms is very noticable. So if the DOM change actually failed in 5ms, I'd rather not have a 60ms lag.
You could also take a signal to abort the operation, which is good practice anyway.
We currently have an .abandon()
method on transitions, but I want to rename this .skipTransition()
, or something that communicates that only the transition will be skipped, it won't skip the DOM change (unless you skip that in some other way).
One of the mistakes we made in some of the earlier designs of this API is trying to be a scheduler for the DOM change, but I think we really need to focus on transitions only. The Navigation API provides an abort signal for abandoning DOM changes, and frameworks that perform DOM changes asynchronously already have their own scheduling systems.
Note that the current API can still cause rendering to freeze, if you await a promise that never resolves.
Yeah, although it's generally only faulty promises that do that. Those cases are caught by the timeout. But I'd rather not rely on the timeout for cases where the error can be easily caught.
I’m sure it's possible with every framework, but it's yet another obstacle for authors to figure out which advanced framework feature would do this. E.g. off the top of my head I cannot think of how to reliably do this with Vue, at least not without heuristics and hacks.
I've never written anything in Vue before, but I can try and cook up a demo. Do you have an example of how you'd use something like ResizeObserver
with Vue? That would give me a good start I think.
Here's a demo created in Vue https://8trrx7.csb.app/ (runs in Chrome Canary with the document transition flag).
Here's the code https://codesandbox.io/s/busy-kepler-8trrx7?file=/src/App.vue.
Assuming this is how you'd increment a number in Vue:
export default {
data: () => ({
num: 1,
}),
methods: {
incrementNum() {
this.num += 1;
}
}
};
Here's how you'd increment it with a transition:
import { nextTick } from "vue";
export default {
data: () => ({
num: 1,
}),
methods: {
incrementNum() {
const update = () => (this.num += 1);
if (!self.SameDocumentTransition) {
update();
return;
}
new SameDocumentTransition().prepare(async () => {
update();
await nextTick();
});
},
},
};
This seems… fine? It's roughly the same as the plain DOM example, except for the addition of nextTick
. I've never written anything in Vue before, so maybe I'm breaking some best practices?
A Vue helper could be created for this:
import { nextTick } from "vue";
export function transitionStateChange(stateChangeCallback) {
if (!self.SameDocumentTransition) {
stateChangeCallback();
return;
}
new SameDocumentTransition().prepare(async () => {
stateChangeCallback();
await nextTick();
});
}
Which could be used like this:
export default {
data: () => ({
num: 1,
}),
methods: {
incrementNum() {
transitionStateChange(() => (this.num += 1));
},
},
};
lack of this feature is not the only reason people might want to branch and only have the transitions some of the time. E.g. the
prefers-reduced-motion
media query
Fwiw, we've decided to automatically skip transitions in the prefers-reduced-motion
case, unless the developer opts in. https://github.com/WICG/shared-element-transitions/issues/19#issuecomment-1227075082
Update: This decision was reversed in favour of a solution that covers other types of motion too.
The spec is now at csswg drafts here: https://drafts.csswg.org/css-shared-element-transitions-1/
Heads up, we'll have a session on this feature at TPAC on Wednesday. I'll post the exact time next week. Looking forward to see folks interested there!
The breakout session is on September 14 at 3 PST. Details are here including a link to join virtually.
Hey TAG, I was wondering if you had a chance to take another look at this proposal?
The breakout session is on September 14 at 3 PST. Details are here including a link to join virtually.
Hello, has this session been recorded? It would help us with this review.
Unfortunately we don't have a recording for that session but the scribed minutes are here: https://www.w3.org/2022/09/14-pagetransitions-minutes.html.
And the slides used in the session are here: https://lists.w3.org/Archives/Public/www-archive/2022Sep/att-0007/set-slides.pdf
We looked at this again today and had some questions:
We are particularly concerned about the complexity and ergonomics of this API, and we (TAG members) have not yet had the a-ha moment where we fully understand how all the pieces come together to create a transition in a real use case from scratch. Given the trouble we've had with it, that gives us some pause about DX in the wild. Perhaps it's an inherently complicated problem and there cannot possibly be a simpler solution, but it is a concern that came up several times in our discussions.
@jakearchibald
Regarding https://github.com/w3ctag/design-reviews/issues/748#issuecomment-1225922770 , with the current API, updateTheDomSomehow()
needs to be repeated twice, which is fine when it's a separate function, but it means all such DOM modifications need to become separate functions, which as an author I'd find annoying.
Re:frameworks, I’m sure it's possible, and yes it doesn't look too bad, but it's unfortunate that it requires people to find out implementation details like nextTick()
that don't come up during typical framework usage, and are different per framework. I think the current callback API is less friendly to the multiple layers of abstraction in modern web apps. It's not the end of the world if it doesn't change, but I still think not having a callback would serve the majority of cases better.
Is the current experimental implementation in Chrome up to date with the current syntax?
Yes. Canary is up to date, the last syntax changes landed in 109.0.5410.0.
Do you have feedback from authors that have used the experimental implementation? Have you observed any of them using it and seen what they struggle with?
Feedback from authors has been quite positive since the pseudo-DOM structure can be animated using existing APIs and debugged via existing devtools support. A few interesting patterns which have come up from experimentation are:
Avoiding a long delay when rendering is paused, waiting for the new DOM to load. They'd like some animation to give the user feedback that content is loading.
Keeping an element continuously animating throughout the transition, in particular a video. Again if the author callback is fast then the duration the video doesn't update is limited to how long the browser takes to snapshot which is 2-3 frames long and not noticeable. @jakearchibald made a demo which shows this (works in current chrome canary with chrome://flags/#view-transition enabled).
We had a breakout session describing how the feature works at BlinkOn today. I'll post a link of the recording which would hopefully help with understanding the full design.
Will let @jakearchibald comment on the ergonomics of the callback based API.
@LeaVerou
Regarding #748 (comment) , with the current API,
updateTheDomSomehow()
needs to be repeated twice, which is fine when it's a separate function, but it means all such DOM modifications need to become separate functions, which as an author I'd find annoying.
I don't think this will be a huge issue in practice, as I expect developers will write a little helper function like this:
async function doViewTransition(callback) {
const skipTransition =
!document.startViewTransition ||
matchMedia('(prefers-reduced-motion)').matches ||
localAppConfig.skipTransitions;
if (skipTransition) {
callback();
return null;
}
await document.startViewTransition(callback).domUpdated;
}
Then the usage would be:
await doViewTransition(() => {
header.classList.add('whatever');
});
Even in a world where all browsers support view transitions, a helper is handy for centralising skipping transitions for reasons that are specific to the site (like reduced motion and localAppConfig.skipTransitions
in the above example).
Re:frameworks, I’m sure it's possible, and yes it doesn't look too bad, but it's unfortunate that it requires people to find out implementation details like
nextTick()
that don't come up during typical framework usage, and are different per framework. I think the current callback API is less friendly to the multiple layers of abstraction in modern web apps. It's not the end of the world if it doesn't change, but I still think not having a callback would serve the majority of cases better.
I don't think there's a model we can adopt where you wouldn't need to know about nextTick()
in Vue. In order to create a transition, we need to capture the before state, and the after state. The problem is, Vue abstracts and defers the DOM changes, so you're reliant on the framework providing some sort of signal that it applied the changes you requested, and in Vue that's nextTick()
. If that's tricky… that's on Vue for not providing an easy way to know when the changes you asked for actually happened.
My hope is that each framework will have a hook/component that wraps this, and turns state changes into transitions, similar to what they already do with animations, but with much less code.
If you haven't had an a-ha moment with this, then my docs suck. That's probably because I'm too deep in the spec/implementation. Would you be up for a video call where we talk through the API & examples? Hopefully the a-ha moment will happen, and you can tell me what details should have been at the very top of the docs, in bold, rather than buried somewhere 😄
And here is a recording of the session at BlinkOn. It's focused on Blink/Chromium specific implementation details for some parts but covers the general web platform concepts too.
@jakearchibald and I had a call about this yesterday, and I think I now understand how this works and how the different pieces come together a lot better. We later discussed this again with other TAG members in a breakout.
We are aware that you have all been thinking about this for a lot longer than we have, and it's entirely possible that you've already explored all this and concluded the current API shape is a necessary compromise, but in case it helps, these were our thoughts:
<meta>
element which cannot be customized by media queries or scoped to elements in the future.view-transition-name
is not Web Platform compatible. Typically in the declarative languages of the Web Platform, when something needs to be unique but isn't, is still handled gracefully (e.g. by using the first or the last one). Especially this being a CSS property, it's very easy to accidentally specify it on multiple elements, simply because it's very easy to specify selectors that match multiple elements. Also, since the root has view-transition-name: root
anyway, how do you use that without having duplicate transition names? We wondered if it could prevent some of these issues and allow more room for future expansion if view-transition-name
distinguished between the start and end state (e.g. through old
and new
keywords, possibly used together if the same element is both). That also prevents the user having to time when view-transition-name: foo
will be removed from the start state and added to the end state: both can happen at once, at the same time as the event that kicks off the transition.
In general, we'd like to see a layered, gradual approach, where a small amount of declarative CSS syntax can address common use cases, more CSS can customize the transitions further, and JS is needed to customize behavior even further than that. This is also in line with how existing CSS transitions and animations work, where syntax is gradually added to customize further, but very little syntax is needed to produce an initial result.
We also think tighter collaboration between the designers of this API and the CSS WG could help address some of these issues.
We like that MPA transitions are declarative, and would love to see a declarative syntax for SPA transitions too, as right now the two APIs feel a bit disconnected. It's also an antipattern if an author needs to write JS to animate a DOM manipulation or navigation that was not fired through JS.
Completely agreed with the goal to keep the syntax as declarative as possible. I’ll clarify the challenge with being able to do the work of the JS API with a declarative syntax, and would love to hear TAG’s ideas for it.
The JS API is needed because the UA needs an explicit signal to render and cache the old DOM state before the DOM is changed. This signal doesn't need to be JS-driven, as proven by the MPA version of the API which uses cross-document navigations as the signal. The only difference between the MPA and SPA versions of the API is this signal, the rest is the same. In SPAs, JS is already driving the DOM change, so it seems reasonable for JS to be used to signal these states. It wouldn't be impossible to do this all in CSS (in theory we can add as much syntax as we want), but I'm not confident it would be easier for developers to use.
A good example for a DOM change triggered declaratively would be the :hover pseudo-class. A strawman proposal for a completely declarative view transition in this case would require:
Admittedly I don't know how this could be done in JS, and it attests to your point that there are cases where declarative syntax would be needed. However, that doesn't mean we couldn't add additional ways to provide this signal going forward for such cases. The design of view-transitions allows for this kind of thing to be explored.
A declarative way to transition hash navigations within the same document could address some of that.
+1 for declarative transitions on hash navigations, this has come up before. Filed #8300 to discuss this in CSSWG.
Perhaps also a declarative way to filter view transitions by container and types of DOM mutations?
I’m assuming you mean transitions for changes in a third party component which doesn’t provide the requisite signals required by document.startViewTransition()
. I don’t see how a CSS solution would solve this over JS. It does sound similar to scoped transitions which would allow multiple concurrent transitions on a page, scoped to a DOM sub-tree. This way a third-party widget can have its internal transitions without leaking any details to the embedding page.
It looks like CSS would be a more appropriate place for the declarative opt-in
Agreed. #8048 is tracking that.
We think it's essential to be able to customize the transition easily based on the elements or pages that are participating in it.
There are going to be declarative hooks for the common customization cases in CSS. For instance, based on the old/new Document URL; or by pages assigning themselves a tag/name/class/category which can be used in CSS. It might be more ergonomic to express the customization based on going from “index” to “article" rather than going from /
to /2023/01/happy-new-year/?utm-matrix=abcdef
.
The JS component of the cross-document API could also allow passing opaque contextual data between pages for this customization but this warrants more discussion, filed #8303 for this.
Note that the API surface specific to cross-document/navigation triggered transitions is in very early stages right now. There is a prototype but the MPA API is being iterated on as level 2 of this spec, you can see CSSWG issues here.
We'd like TAG to scope this review to the CSS component of this API, which is common to SPA/MPA, and the SPA specific JS API.
Especially this being a CSS property, it's very easy to accidentally specify it on multiple elements, simply because it's very easy to specify selectors that match multiple elements.
The rationale for skipping the transition was to make it easier for developers to spot an error with a hard failure rather than a graceful fallback which masks it. If the view-transition-name
is not unique and wrong elements are matched together, the transition could look very incorrect visually. Skipping it allows developers to fallback to old behaviour and also monitor how often it's happening, in case they are hitting a bug in production.
There are use-cases which need a common identifier for a set of tagged elements, discussion at #8209. This can be added in the future, we haven't designed ourselves in a corner here.
Also worth noting is the design principle that Transitions are an enhancement, so we don't see skipping the transition as a failure. We might, on really low-end devices, or devices in some constrained mode, choose to always skip transitions.
since the root has view-transition-name: root anyway, how do you use that without having duplicate transition names?
Developers can override this with a style rule, similar to overriding any other rule in UA CSS.
We wondered if it could prevent some of these issues and allow more room for future expansion if view-transition-name distinguished between the start and end state
It isn't clear what problem this solves, duplicate view-transition-names
would be a developer bug. I understand that ensuring this in CSS can be difficult but it's a fundamental requirement to ensure correct matching of DOM elements for animations.
That said, your suggestion for distinguishing view-transition-name
between start/end states can be added in the future if a use-case emerged. For example, view-transition-name-old
only applied when discovering elements in the old DOM and view-transition-name-new
only for elements in the new DOM. The existing view-transition-name
then becomes a shorthand which sets both, preserving current behaviour.
That also prevents the user having to time when view-transition-name: foo will be removed from the start state and added to the end state: both can happen at once, at the same time as the event that kicks off the transition.
A pattern I’ve seen to do this is toggling the class name on the root element:
document.documentElement.classList.add(“transition-old”);
document.startViewTransition(() => {
document.documentElement.classList.remove(“transition-old”);
document.documentElement.classList.add(“transition-new”);
});
}
html.transition-old .element-foo {
view-transition-name: target;
}
html.transition-new .element-bar {
view-transition-name: target;
}
There are probably better ways to do this. Given that the author already has to time their DOM update in the callback passed to startViewTransition, it doesn’t seem like an issue to have to update view-transition-name
as a part of the DOM update?
In general, we'd like to see a layered, gradual approach, where a small amount of declarative CSS syntax can address common use cases, more CSS can customize the transitions further, and JS is needed to customize behavior even further than that. This is also in line with how existing CSS transitions and animations work, where syntax is gradually added to customize further, but very little syntax is needed to produce an initial result.
IMHO the API already requires minimal CSS for the common use cases. For a root transition which is a simple cross-fade, no CSS is necessary. For transitions morphing one widget into another, the developer needs to only add view-transition-name
to the corresponding DOM elements. Transitions can be customized further with more CSS or JS, and built on top of existing animation primitives on the web (CSS animations or web animations). But none of that is needed if the default UA animations suffice.
What we haven’t been able to do is a no JS syntax for basic SPA transitions. You need at least 1 line of JS to make that work:
document.startViewTransition(updateToNewState);
And that’s because any basic SPA app will drive DOM changes via JS internally. They will already have script which invokes updateToNewState along with mutations to the navigation history. So it doesn’t seem too difficult to wrap that call in the syntax above. We could consider invocations to history.pushState
as an implicit signal for triggering a transition. But now we’re adding unnecessary tight coupling with the navigation APIs. And there will still be cases where DOM changes don’t necessarily mutate the navigation history.
So the minimal JS API is unavoidable for the same-document (SPA) API. The MPA API can be script free because these JS events are implicit in the navigation. A JS free API for same-Document transitions is definitely useful for a subset of cases but doesn’t need to be a goal for v1 of this feature.
We also think tighter collaboration between the designers of this API and the CSS WG could help address some of these issues.
I believe this is already the case. We had multiple breakout sessions with CSSWG during the early incubation period. Those sessions are recorded in #6464, which also includes WG’s resolution for the spec to go to FPWD. All non-trivial changes have gone through CSSWG resolutions as well. I’m happy to take suggestions for more engagement with CSSWG.
Thank you for being responsive to our feedback. We circled back at this today in a breakout and we have consensus that our work here is done for now. We do have concerns about this API, discussed above, but are also optimistic that these are being worked out, and we do think this is functionality that is very useful for developers. We are looking forward to seeing how this API evolves to be more usable in a wider variety of use cases. We will close this for now, feel free to re-request a review if you make substantial changes.
Wotcher TAG!
I'm requesting a TAG review of View Transitions.
View Transitions is a proposal for a new web API that allows a simple set of transition animations in both Single-Page Applications (SPAs) and Multi-Page Applications (MPAs).
The inspiration for this feature are transitions similar to the ones listed in the Material Design Principles. The intent is to support transitions similar to Android Activity Transitions.
Further details:
We'd prefer the TAG provide feedback as (please delete all but the desired option):
☂️ open a single issue in our GitHub repo for the entire review