vidscroll0.3.3

React

Add a scroll video to any React 18 or 19 app.

vidscroll works with React 18 and 19 in any bundler setup, such as Vite, Next.js or Astro. Its one dependency, the video library it uses to read and re-encode files, is loaded in the browser only when a video needs it.

Set it up

  1. Install vidscroll.

    npm install vidscroll
  2. Put a video in your public folder. Any MP4 or WebM works. In most setups, public/videos/hero.mp4 is served at /videos/hero.mp4.

  3. Add ScrollVideo where the video should appear.

    Hero.tsx
    import { ScrollVideo } from "vidscroll";
    
    export function Hero() {
      return <ScrollVideo src="/videos/hero.mp4" length="400vh" />;
    }

That’s a working scroll video. The rest of this page covers showing content over it, how it sits in your layout, and preparing the file for production.

Show content over the video

Children of ScrollVideo are drawn over the video. Wrap them in Section to show them over part of the scroll:

Hero.tsx
import { ScrollVideo, Section } from "vidscroll";

export function Hero() {
  return (
    <ScrollVideo src="/videos/hero.mp4" length="400vh">
      <Section start={0} end={0.3}>
        <h2>Doors close</h2>
      </Section>
      <Section start={0.55} end={0.9}>
        <h2>Open water</h2>
      </Section>
    </ScrollVideo>
  );
}
Scroll inside the preview
Example.tsx
import { ScrollVideo, Section } from "vidscroll";

export default function Example() {
  return (
    <ScrollVideo src="/train.mp4" length="400vh">
      <Section id="doors" start={0} end={0.3}>
        <h2>Doors close</h2>
      </Section>
      <Section id="water" start={0.55} end={0.9}>
        <h2>Open water</h2>
      </Section>
    </ScrollVideo>
  );
}

start and end are positions in the scroll, from 0 to 1. Sections can also use seconds (fromTime, toTime) or frame numbers. Content outside a Section stays on screen the whole time.

How it sits in your page

ScrollVideo renders a full-width block. Its height is length plus one screen: with length="400vh", the block is five screens tall, and the video is pinned for the first four while the page scrolls past. Content before and after it scrolls normally, and a page can hold several scroll videos.

Set length explicitly when content follows the video. Then the block has its full height from the first render (on the server too), and nothing below it moves. With the default "auto", the length comes from the video’s duration, which is only known once it loads, so anything below moves down at that point.

What visitors see while it loads

MomentOn screen
DownloadingThe video’s first frame behind a loading bar, or your poster image.
PreparingA short pass over the timeline so later jumps are fast.
Re-encoding (first visit only)The original video, already scrollable, with a small progress badge in the corner. Only for files that aren’t prepared.
ReadyThe video, following the scroll.

All of this can be customised or turned off with the loader and poster props. See ScrollVideo.

Prepare the video for production

Smooth scrubbing needs keyframes close together, and most exported videos have them seconds apart (why). vidscroll fixes that in the visitor’s browser on their first visit, but that costs them a few seconds. Preparing the file once, before you deploy, skips that step for everyone.

The vidscroll encode command does it. It uses ffmpeg, the standard command-line video tool, which vidscroll doesn’t bundle because it’s a large native program. The easiest way to get it is the ffmpeg-static package. It’s a development dependency: it runs on your machine or in CI and is never sent to visitors.

  1. Get ffmpeg. Skip this if ffmpeg already works in your terminal.

    npm install -D ffmpeg-static
  2. Prepare the file.

    Terminal
    npx vidscroll encode public/videos/hero.mp4
  3. Use the prepared file. The command writes hero.scroll.mp4 next to the original.

    Hero.tsx
    <ScrollVideo src="/videos/hero.scroll.mp4" length="400vh" />

The prepared file has a keyframe every 10 frames, is at most 720p on its short side, has no audio track, and is set up to start playing before it finishes downloading. Options such as --resolution 1080 are listed in the CLI reference.

Next steps