image-sequence-scrubbing
Image Sequence Scrubbing
When to use this
- You want a product reveal where scrolling frame-steps through a pre-rendered sequence (a 3D render turntable, an exploded-view animation, a physical product being unboxed), the technique Apple popularized on product pages.
- You need frame-perfect scroll sync where a canvas draw call is cheaper and more controllable than a video element's
currentTimeseeking, which has inherent seek latency. - You have pre-rendered frames (from Blender, After Effects, or a real photo/video shoot) and need a loading strategy that does not block first paint or blow the page's data budget.
- Do NOT use this for actual video content the user might want to just play normally (testimonials, tutorials); use a normal
<video>element. Do NOT use this for anything achievable with CSS/SVG animation, canvas sequences are for cases where the visual genuinely comes from pre-rendered raster frames.
Mental model
The technique is: pre-render N frames of an animation as individual images, map scroll progress (0-1) to a frame index (0 to N-1), and on every scroll tick draw the current frame into a <canvas>. The canvas approach beats a <video> element with currentTime scrubbing for interactive frame-stepping because video seeking is asynchronous and throttled by the browser's decoder (seeked event can lag scroll input by 50-150ms depending on codec and keyframe distance), while drawing a already-decoded ImageBitmap to canvas is synchronous and sub-frame-cheap.
The two hard problems are frame count math and preloading. Frame count math: you need enough frames that adjacent frames don't show a visible jump (the "flip book" test — at normal scroll speed, consecutive frames should differ by roughly 1-3 degrees of rotation or equivalent visual delta, not 10+), but not so many that total download size becomes unreasonable. A typical product turntable covering a full 360-degree rotation over a comfortable scroll distance uses 60-150 frames; a shorter directional reveal (product assembling itself) might only need 30-60.
Preloading strategy: you cannot wait for all N frames to download before starting, that blocks interaction for seconds. The standard approach is to preload a small initial batch (enough to cover the first screen's worth of scroll) eagerly, then stream the rest in the background sorted by proximity to current scroll position, using decode() on each Image/ImageBitmap before it's needed so the actual draw call never blocks on decode (decode is where most per-frame canvas jank comes from if skipped). createImageBitmap() is preferred over plain <img> + drawImage because it decodes off the main thread and can be drawn without triggering the image's own layout/paint machinery.
Size budgeting matters more here than almost any other web animation technique because you are shipping dozens to hundreds of images. A single reveal sequence at reasonable canvas resolution (e.g. 1200x800 WebP at quality 75) commonly runs 15-40KB per frame; 90 frames at 25KB average is ~2.25MB, which is a real, user-felt payload on mobile connections and needs explicit budgeting decisions (resolution, frame count, format) rather than defaulting to "render at full resolution and see."