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.
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();