Video scrub
Video scrub maps scroll progress directly to a video's playback position — scroll down and the
video plays forwards, scroll back up and it rewinds, frame by frame. It's normally used full-bleed
inside a pinned scene, but works on any <video>.
See it live: Video scrub on fancyanimations.com.
How it works
- Add the Video scrub preset on a
<video>element itself, or on a wrapper that contains one — the plugin finds its first<video>descendant automatically. - Each video is driven by exactly one element — the first one in the page that finds it. It's
fine for both a wrapper and its inner
<video>to carry the setting. - The plugin automatically pauses the video and sets
preload="auto"if you haven't set a preload value yourself. You don't need autoplay or loop turned on. - Like any row, it has a Start/End/From/To/Invert window — here, From/To describe what part of the video plays, as a percentage of its total duration.
| Setting | Meaning |
|---|---|
| Start / End | The scroll window (same as any row) |
| From / To | The part of the video played, as % of its duration |
| Invert | Plays backwards |
There is no --fa-scroll--video-scrub variable for custom CSS — if you need something else (a
crop, a caption) tied to the same scroll range, add a separate Custom row to the same element.
Video scrub also has its own easing, tuned to how long the browser's decoder takes to seek —
"Scroll smoothing" doesn't apply to it. "Sequence children" doesn't count Video scrub rows either.
Typical structure
A pinned scene: the video sits absolute and full-bleed inside the stage, with "Get parent" so its playback position follows the whole section rather than itself, and its window set to the scene's pinned window (e.g. 25–75% in a 300vh scene). Captions usually get their own rows on the same timeline — see Advanced recipes.
The video file decides whether it's smooth
Scrubbing seeks through the file frame by frame, and two encoding choices make that visibly jump:
| Problem | Fix |
|---|---|
| Too few keyframes — every seek takes tens to hundreds of milliseconds | Re-encode with every single frame as a keyframe (in HandBrake: Extra Options keyint=1) |
| Too few frames for the scroll distance (4s at 30fps = 120 frames covering ~2700px of scroll) | About 300 frames or more — e.g. 6 seconds at 60fps |
A Check video button appears next to every scrubbed video in the Bricks, Etch and block editor canvases. It measures the real file: the average time a seek actually takes (over 50ms means it needs more keyframes) and estimates the total frame count (under 300 looks jumpy). It never appears on the published, visitor-facing site.
- Bricks
- Etch
- Gutenberg
Use the native Video element with source "Media", and add a Video scrub row in the Style tab → ScrollTrigger Animations panel. A few things specific to Bricks' video element:
- Leave Controls off and set the element's pointer events to "none" — without controls, Bricks still adds its own click-to-play/pause behaviour, and a click would interrupt the scrub.
- Leave Preload empty so the plugin can set
preload="auto"for you. - The native "Object fit" control targets the inner
<video>correctly; forobject-position, write it in custom CSS targeting the tag directly (%root% video { object-position: 50% 30%; }) — Bricks renders a wrapperdivaround the video and the Fancy classes land on both.
Add the Video scrub class (and its window classes) to the <video> element itself, or to a
wrapper around it. Style the <video> tag itself (object-fit, object-position) in its own
class as usual.
Use the native Video block, turn on "Enable scroll trigger" and choose "Video Scrub" as the
Animation. The class lands on the block's own wrapper, and the plugin finds the <video> inside
it automatically.
Common problems
- The video advances in small, visible jumps. Almost always too few frames for the scroll distance, not a plugin issue — aim for 300+ frames, and confirm with Check video (3.2.0+) or by testing in real Chrome (some automated test browsers don't decode H.264 video at all).
- Every seek lags behind the scroll. The keyframe interval is too long — re-encode with every frame as a keyframe.
- A click on the video pauses or restarts it. That's Bricks' own click handler on a controls-less video — set pointer events to "none" on the element.
object-fithas no visible effect, or the video shows letterboxing. It was applied to the wrapperdivrather than the<video>tag itself — use the native Object fit control, or target.your-class video { }directly in custom CSS.