All-intra, and why our hero scrubs instead of plays
Every frame a keyframe costs eight times the bytes and buys you the one thing a scroll-scrubbed hero needs: an instant, exact seek.
The home page of this site does not play a video. It holds one still, and your scroll wheel decides which still that is. The difference sounds academic until you try it with a normal export and watch the thing stutter like a fax machine.
The problem with a normal export
A standard H.264 file is mostly P- and B-frames — frames that describe the difference
from a neighbour rather than the picture itself. Full pictures, keyframes, land maybe once
every two seconds. When you set currentTime to something between them, the decoder has to
walk back to the nearest keyframe and replay everything in between before it can paint.
At 60 scroll events a second, that walk never finishes. You get the slideshow.
All-intra: every frame a keyframe
Export the same ten seconds as all-intra — ProRes, DNxHR, or H.264 with the GOP length set to one — and every single frame becomes a complete picture. A seek to 4.18 seconds decodes exactly one frame. The cost is size: our 241-frame master is roughly eight times the bytes of the equivalent long-GOP file.
That trade is the right way round for a hero. You pay once, at load, for a scrub that never misses.
Buffer it as a blob before you scrub it
The second half of the trick is where the bytes live. Point a <video> at a URL and every
seek becomes an HTTP range request, and every range request is a round trip. Fetch the file
once into a Blob, hand the video an object URL, and the seeks are local.
const res = await fetch(VIDEO_URL);
const blob = await res.blob();
clip.src = URL.createObjectURL(blob);
Show a real progress bar while that happens — you are asking for ten megabytes up front and people will wait for it if you tell them what they are waiting for.
Ease the seek, do not snap it
Last piece. Do not assign the scroll position straight to currentTime. Keep a target and
chase it inside your animation frame:
seekAt += (seekTo - seekAt) * 0.115;
if (clip.readyState >= 2 && !clip.seeking) clip.currentTime = seekAt;
That single lerp is the difference between footage that reacts and footage that feels attached to your hand.