Skip to content

Transitions

js
import { 
enterTransition
,
leaveTransition
,
transition
} from '@studiometa/js-toolkit/utils';

Promoted into core from the migration set, beside the easings, spring() and smoothTo().

transition

ts
transition(
  el: HTMLElement,
  nameOrStyles: string | TransitionStyles,
  mode?: 'keep' | 'remove',
): Promise<void>
ts
interface TransitionStyles {
  from?: string | string[] | Partial<CSSStyleDeclaration>;
  active?: string | string[] | Partial<CSSStyleDeclaration>;
  to?: string | string[] | Partial<CSSStyleDeclaration>;
}

The three-state dance every CSS transition needs, with the frame boundaries in the right places:

js
import { 
transition
} from '@studiometa/js-toolkit/utils';
const
el
=
document
.
body
;
async function
run
() {
// The class form: `fade-from`, `fade-active`, `fade-to`. await
transition
(
el
, 'fade');
// The inline-style form. await
transition
(
el
, {
from
: {
opacity
: '0' },
active
: {
transition
: 'opacity 300ms' },
to
: {
opacity
: '1' },
}); // Keep the `to` state when it ends. await
transition
(
el
, 'fade', 'keep');
}

mode decides what happens at the end: 'remove' (the default) clears the states, 'keep' leaves the to state applied.

Each of the three states takes a class name, an array of class names, or an inline-style object — setClassesOrStyles() is what makes that work.

The two directions

ts
type TransitionOptions = {
  enterFrom: string;
  enterActive: string;
  enterTo: string;
  enterKeep: boolean;
  leaveFrom: string;
  leaveActive: string;
  leaveTo: string;
  leaveKeep: boolean;
};

One option object, so a component declares eight options once and both calls read the same thing:

js
await enterTransition(this.$el, this.$options);
await leaveTransition(this.$el, this.$options);

enterTransition

ts
enterTransition(el: HTMLElement, options: TransitionOptions): Promise<void>

leaveTransition

ts
leaveTransition(el: HTMLElement, options: TransitionOptions): Promise<void>

TRANSITION_OPTIONS

ts
const TRANSITION_OPTIONS: Record<string, OptionDefinition>;

That option set as a config.options fragment, ready to spread:

js
static config = {
  name: 'Panel',
  options: {
    ...TRANSITION_OPTIONS,
    open: Boolean,
  },
};

Which means the eight names, their types and their defaults are declared in one place, and a component that wants them writes one line.

Testing them

A transition's end state is asserted by polling, never by settle()

A method that starts a transition does not hand it back, and a kept end state lands only after nextFrame(), the from and active states, and either a transitionend or one more frame. settle() is generous rather than deterministic, which is a flake that passes alone and fails under load.

Polling for an absence is wrong for the mirror-image reason: leaveTransition() clears the other direction's to state synchronously, so the poll passes before anything has happened.

See waitFor().

What is not here

Time-based playback, stagger, sequencing, morphing and text splitting are the separate ui-animation package. tween and animate are not shipped.

exit, layout and layoutId are not an engine's job — viewTransition() solves them, and it is in core.

MIT Licensed