Load it all, once.
Frames come in under a concurrency cap with a progress callback, so you can hold the section behind a loader bar instead of showing a half-drawn sequence.
VIPEX Motion · 05 Scroll
Scroll drives the frame. One engine either seeks a pinned video through a queue that never lets two seeks fight, or steps a canvas through a preloaded image sequence — same options, same controller, same pin.
scroll ↓ · press H to toggle the panel
A canvas image-sequence player. Frames preload eight at a time behind a progress callback, draw cover-fit at device pixel ratio, and the frame index eases toward the scroll target so the motion never looks stepped.
generating 120 frames…
Frames come in under a concurrency cap with a progress callback, so you can hold the section behind a loader bar instead of showing a half-drawn sequence.
The index is eased, not snapped. Flick the wheel and the sequence catches up over a few frames rather than jumping — set lerp to 0 for a hard lock to the scrollbar.
Give it a hold tail and the last frame stays on screen while the reader finishes the section, instead of the pin releasing the moment the sequence ends.
A pinned clip seeked by scroll position. Two chapters split the track — the second one plays in reverse and dissolves in over the boundary. Every seek waits for the previous one to settle, so a fast flick queues one pending target instead of a hundred.
checking for a runtime encoder…
This browser has no usable MediaRecorder + canvas.captureStream
pair, so there is no clip to bake and no network to fetch one from. The section below
runs the frames path instead — labelled, not faked. Point the engine at your own
file and the video path takes over:
ScrubFX.init(document.querySelector(".scrub"), {
mode: "video",
chapters: [
{ src: "clip-a.mp4", from: 0, to: 0.5 },
{ src: "clip-b.mp4", from: 0.5, to: 1, reverse: true }
],
pinHeight: 400, crossfade: 0.07, lerp: 0.12
});
Encode every frame as a keyframe or the seeks land on the nearest one and the clip appears to hold:
ffmpeg -i in.mp4 -c:v libx264 -preset slow -crf 20 \ -g 1 -keyint_min 1 -sc_threshold 0 -pix_fmt yuv420p \ -movflags +faststart out.mp4
Scroll writes currentTime. A new request while a seek is in flight replaces the pending one, so the decoder is never handed work it will only throw away.
The second clip owns the back half of the track, dissolves in across the boundary and runs in reverse. Both clips are live through the crossfade.