Insight · ·

Scroll-Scrubbed Video on the Web, and How to Keep It at 60fps

Reliable scrubbing starts before animation code. Blob playback, coalesced seeks, decoder-led codecs and a flat phone layout made the effect shippable.

Scroll-Scrubbed Video on the Web, and How to Keep It at 60fps

The scroll progress was correct and the requested video time was correct. The picture still returned to frame zero on every seek. The animation code looked guilty because the visible symptom followed scroll, but the failure had begun at the network response.

Our scroll-scrubbed build became reliable only after treating delivery, seeking, codec choice and layout measurement as one system. Mapping scroll to currentTime was the smallest part.

Fetch the clip before scrubbing

Plain src playback depends on the video host serving byte ranges correctly. Without usable range support, arbitrary seeks can clamp back to frame zero because the browser cannot obtain the required media segment.

We fetched the complete clip as a Blob, created an object URL and assigned that URL to the video. The decoder then sought against a local resource with predictable access. The object URL must be revoked when the component is permanently disposed, but not while the media element still depends on it.

This choice trades startup transfer and memory for reliable local seeking. It suits a deliberately bounded hero clip, not an unexamined library of long videos.

Coalesce seeks around the decoder

Scroll can request new times faster than the decoder can complete them. Assigning currentTime again while the element is still seeking does not create a trustworthy queue. Requests may be dropped, and on phones the decoder can become wedged.

We kept one pending target. When scroll produced a new target during a seek, it replaced the pending value instead of touching the video immediately. On seeked, the controller applied the latest pending target. Intermediate positions disappeared by design because displaying an old intermediate frame after scroll had moved would add latency, not fidelity.

We also skipped targets closer than half a frame to the current target. Those seeks cannot create a meaningfully different displayed frame, but they can keep the decoder busy. Scrubbing needs backpressure, not a stream of every scroll sample.

Let the decoder choose the codec

We prepared AV1, then HEVC, then H.264 and asked the browser what it could decode instead of guessing from device names. The first accepted source became the Blob candidate. This reduced the desktop clip from 11.4MB to 3.5MB when AV1 was available.

Codec selection is only useful when each asset is encoded for seeking, not merely for the smallest total file. Keyframe spacing affects how much work an arbitrary seek creates. Test the real scrub path on the target browser. A supported codec is not automatically a responsive encode.

Keep H.264 as the broad fallback and make source choice observable in diagnostics. Otherwise device-specific failures arrive without knowing which asset played.

Phones get a different composition

We do not scrub the video on phones. The section lays out flat and uses ordinary visual progression instead. Mobile decoders, memory pressure, thermal limits and browser lifecycle behaviour make the same effect expensive and fragile. A degraded scrub with missed frames is not a premium version of the story.

This is product scope, not a media query added after performance fails. The mobile layout preserves the content order and meaning without depending on frame-accurate control. Reduced-motion users should receive the same stable path.

On larger screens, the controller pauses work when the section is outside the useful viewport and releases resources when the experience ends.

Re-measure after fonts load

The pinned section measured its start and travel distance correctly during the initial layout. Then the web font arrived, text reflowed and those offsets became stale. On a first visit the effect looked broken. On later visits the cached font made the initial measurement appear correct.

We re-measured after document.fonts.ready, as well as on relevant resize and content changes. The scroll controller kept one source of geometry rather than allowing the pinning layer and video mapping to calculate separate ranges.

To implement this week, verify range behaviour first, then test Blob seeking in isolation. Add a coalescing controller with half-frame suppression. Log selected codec, seek duration and dropped requests. Flatten the phone layout before tuning desktop polish. Finally, test a cold first visit with fonts uncached. Scroll-scrubbed video holds together when the decoder and document geometry set the pace, not the scroll event.