viewTransition
viewTransition(update: () => void | Promise<void>): Promise<void>Runs a DOM update inside a native view transition, with a progressive-enhancement contract: where the platform has no startViewTransition, the update simply runs.
Usage
import { viewTransition } from '@studiometa/js-toolkit';
async function replace(el: Element, html: string) {
await viewTransition(() => {
el.innerHTML = html;
});
}Batching
Updates queued in the same flush batch into one startViewTransition() call, so a backdrop and a panel animate as one transition rather than two fighting each other.
Each later batch is appended to one promise tail, so several flushes during one running transition stay serialized instead of interleaving.
The scheduler flushes the pending write tasks before the snapshot, so a pending write is part of the "before" state rather than landing mid-transition. Writes scheduled inside the update callback run within the transition.
It is standalone
Base has no view-transition method and no import of one. The helper is a free function, and the whole view-transition graph stays out of the Base module graph — a page that never calls it never downloads it.
Composing it
It is a DomUpdateRunner, so an ancestor can claim a negotiated update with it:
this.$on(EVENTS.dom.update, ({ detail }) => detail.wrap(viewTransition));and it is a SwapWrap, so a swap() can play as one transition:
await swap(el, html, { wrap: (mutate) => viewTransition(mutate) });Neither is the default. The ancestor chooses the lane, because it knows whether the region animates.
exit, layout and layoutId
They are not an animation engine's job. Native view transitions solve them, which is why this is in core while tween and animate are not shipped at all.
@studiometa/ui keeps a declarative ViewTransition component, rebuilt on this helper.