UIUX Learning

Scrubbing an AI-generated video with scroll: one timeline, six chapters

Jun 23, 2026 · 3 min read
Four of the six chapters of “What is jirai-kei?” An AI-generated film, scrubbed frame by frame as you scroll.

I wanted to try one thing: drive a video from start to finish with scroll alone — no play button. How far you scroll is where the film seeks; stop and it freezes. The subject is a short AI-generated film introducing the jirai-kei subculture, split into six chapters with Traditional-Chinese + Japanese captions, with Lenis for smooth scroll.

👉 See the live page — scroll slowly and the film tracks your motion.

Scroll progress = the film’s timeline

The whole page is a single video pinned centre-screen, backed by 700vh of scroll length as a “scrub track”. Each frame I read scroll progress p (0 to 1):

const total = scrollEl.offsetHeight - innerHeight;   // scrollable distance
const p = clamp(-scrollEl.getBoundingClientRect().top / total, 0, 1);

Multiply p by the film’s duration and that’s the time to seek to. No timeline library, no play/pause/reverse — state is purely a function of scroll position, so scrolling back up rewinds the film for free.

Eased seeking is what makes the scrub smooth

Setting film.currentTime = p * duration directly makes the scrub jump frame to frame — harsh. Instead I ease (lerp) toward the target each frame:

const target = p * film.duration;
currentTime += (target - currentTime) * 0.14;   // 0.14 ≈ smoothness
if (film.readyState >= 2) film.currentTime = clamp(currentTime, 0, duration - 0.04);

0.14 is the balance between responsiveness and smoothness — higher jumps, lower drags. With Lenis smoothing the wheel on top, the scroll itself feels fluid, and the film reads like “holding a timeline” rather than “dragging a scrubber”.

Tying six captions to the film

Captions don’t morph continuously — they switch in discrete chapters: Math.floor(p * 6) slices progress into six bands, each toggling its .cap and the matching chapter dot’s on class, while the top progress bar width is simply p × 100%. The same p drives three things at once — film time, current chapter, progress bar — so they always agree.

What I learned

1. To scrub a video, you have to buffer it. <video> with preload="auto" plus playsinline muted is what allows instant seeking; on phones the file must buffer enough to scrub smoothly, so a single 720p, 30-second file is easier to manage than per-chapter clips.

2. Poster + preloader avoid the blank flash. While the video isn’t ready, the chapter-1 poster shows (poster="ch1.jpg"), and the preloader hides on canplay (or after load) — no black gap.

3. Always honour prefers-reduced-motion. For reduced-motion users: skip Lenis and set currentTime directly (no 0.14 easing) — the film still tracks scroll, just without the extra inertia and smoothing.

4. Scrub smoothness vs. sharpness is a trade-off. Seeking a too-large file stutters on slow machines; too small loses detail. A single 720p file splits the difference.

Wrap-up

From “drive a video with scroll alone” to a page where the film tracks your motion and six captions surface as you go, the biggest takeaway: bind the film’s timeline to scroll and the viewer shifts from “watching a video” to “holding the timeline”. They control the pace, but every scroll position lands on a frame.