Skip to content

viewTransition

ts
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

ts
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:

js
this.$on(EVENTS.dom.update, ({ detail }) => detail.wrap(viewTransition));

and it is a SwapWrap, so a swap() can play as one transition:

js
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.

MIT Licensed