Documentation

Controlling playback

Drive one transition from buttons, a slider, scroll position or your own timeline.

VisDelta has no built-in controls. Every control calls the same controller methods: progress(), play() and pause(). The player on this site is built exactly this way.

Preparing transition
0.00

Buttons

const change = await transition(from, to, { target: '#chart' });

playButton.addEventListener('click', () => change.play({ duration: 800 }));
reverseButton.addEventListener('click', () => change.play({ from: 1, to: 0 }));
pauseButton.addEventListener('click', () => change.pause());

On a pair controller, play() starts from 0 unless you pass from. To resume from the current frame, pass it explicitly:

resumeButton.addEventListener('click', () => {
  change.play({ from: change.value, to: 1 });
});

A slider

slider.addEventListener('input', () => {
  change.progress(slider.valueAsNumber); // 0 … 1
});

progress() pauses any running playback, so a reader can grab the slider mid-animation.

Scroll position

Use the tested, single-file scroll story for step cards, holds and replay. It measures actual card positions, coalesces events and includes explicit handling for content-height iframes. Mapping progress from an iframe's own viewport cannot reveal a cross-origin parent's scroll position.

All control modes call the same progress() API, so reverse scrolling needs no separate chart animation.

Your own clock

Because progress is just a number, VisDelta can follow any timeline, such as a media element, an animation library or a test harness:

video.addEventListener('timeupdate', () => {
  change.progress(video.currentTime / video.duration);
});

Reflecting progress in the UI

play() animates internally. Read change.value on each animation frame to keep a slider or label in sync while it plays:

function playAndTrack(to: number) {
  change.play({ from: change.value, to });
  const tick = () => {
    slider.value = String(change.value);
    if (change.value !== to) requestAnimationFrame(tick);
  };
  requestAnimationFrame(tick);
}

Respecting reduced motion

Don't start animations automatically for readers who prefer reduced motion. A reader-initiated control is still fine, as is jumping straight to the end state:

const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (reduce) change.progress(1);
else change.play();

On this page