Scroll-driven video sections without wrecking performance
How to build a pinned, scroll-linked video and gallery section that feels cinematic but stays fast on phones: one progress value, one playing video, no libraries.
3 min readGryffin Forge
Premium websites love a pinned section where video opens up as you scroll and captions change chapter by chapter. It is also one of the easiest ways to make a page janky, heavy and battery-hungry. Here is how the showcase section used across the demo library is built to stay fast.
One progress value drives everything
The section is a tall container with a sticky stage inside. On scroll, the script computes a single number — how far through the container the visitor is, from 0 to 1 — and everything else derives from it:
- the clip-path inset that opens the framed video to full-bleed,
- a subtle scale on the active video,
- which chapter (clip and caption) is active,
- the progress bar width,
- the title's fade and drift.
Values are written as CSS custom properties, and the CSS does the rest. There is no per-element animation timeline to keep in sync and no animation library to download.
const p = clamp(-rect.top / (rect.height - innerHeight));
stage.style.setProperty('--p', p);
Batch reads and writes in one frame
Scroll events fire faster than the screen refreshes. The handler only schedules work with requestAnimationFrame, and each frame reads layout first and writes styles after. Mixing reads and writes forces repeated layout, which is the most common cause of scroll jank.
Only one video plays
Three autoplaying videos at once will heat a phone and burn data. The rules:
- The first clip loads eagerly so the section is never empty.
- Other clips start with
preload="none". - When a chapter becomes active, its clip plays and the next clip is upgraded to preload.
- Inactive clips pause after their fade-out finishes.
- When the whole section leaves the viewport, everything pauses.
All clips are muted, looped and playsinline, which is required for autoplay on mobile browsers.
Animate cheap properties
opacity, transform and clip-path can be composited by the GPU. Animating top, width or background-position on large elements triggers layout or repaints. The horizontal gallery moves with translate3d, and per-card parallax shifts an inner image with a transform rather than changing its position.
Serve video sensibly
The clips are 720p, a few megabytes each, served through an edge proxy that caches them and supports HTTP range requests so browsers can seek and stream. Posters come from still images so something meaningful appears before the first frame decodes.
Respect reduced motion
When the visitor has asked their system for reduced motion, the section stops pinning, the gallery becomes a normal scrollable row and text appears without animation. The content is identical; only the choreography is removed.
Defend your styles
This section is injected into many different templates, and one of them had a generic .gal grid rule that silently collapsed the gallery track. Every class in the component is now prefixed. If you build components that get dropped into pages you don't control, namespacing is not optional.
Summary
- Derive everything from one scroll progress value.
- Schedule with
requestAnimationFrame; read, then write. - One playing video; preload just in time; pause off-screen.
- Animate only compositor-friendly properties.
- Cache and range-serve video at the edge.
- Honour
prefers-reduced-motion. - Namespace injected styles.