vidscroll0.3.3

Next.js

Use vidscroll in the App Router, from server components or client components.

Set it up

  1. Install vidscroll.

    npm install vidscroll
  2. Put the video in public/. A file at public/videos/hero.mp4 is served at /videos/hero.mp4.

  3. Use ScrollVideo in a page. No "use client" needed:

    app/page.tsx
    import { ScrollVideo, Section } from "vidscroll";
    
    export default function Page() {
      return (
        <main>
          <ScrollVideo src="/videos/hero.mp4" length="400vh" easing="inOutSine">
            <Section start={0} end={0.3}>
              <h2>Doors close</h2>
            </Section>
          </ScrollVideo>
        </main>
      );
    }

Server component or client component?

Both work, and the video behaves exactly the same either way. vidscroll’s components are marked "use client" inside the package, so when you use them from a server component, Next.js treats them as client components automatically. What changes is only what you can pass to them.

From a server componentFrom your own client component
Rendered on the serverYesYes, Next.js renders client components on the server too
Where the video runsIn the browser, after the page loadsThe same
Props you can passPlain data: strings, numbers, booleans, objectsAnything, including functions
HooksNot availableAvailable inside ScrollVideo
Scrolling and loadingIdenticalIdentical

So start in a server component, and move to a client component only when you need one of these:

  • a custom easing function (a preset name such as "inOutSine" works anywhere)
  • onLoad, onError or a loader function
  • the hooks useScrollVideo, useScrollVideoState or useScrollVideoUpdate

Using functions and hooks

Put the scroll video in your own component that starts with "use client", then use that component from any page:

app/hero.tsx
"use client";

import { ScrollVideo, Section, useScrollVideoState } from "vidscroll";

function Timecode() {
  const { time } = useScrollVideoState();
  return <p>{time.toFixed(1)} s</p>;
}

export function Hero() {
  return (
    <ScrollVideo
      src="/videos/hero.mp4"
      length="400vh"
      easing={(t) => t * t}
      onLoad={({ source }) => console.log("served as", source)}
    >
      <Section start={0} end={1}>
        <Timecode />
      </Section>
    </ScrollVideo>
  );
}
app/page.tsx
import { Hero } from "./hero";

export default function Page() {
  return <Hero />;
}

Hooks work in any component rendered inside ScrollVideo, like Timecode above.

What the server sends

Nothing video-related happens on the server: the file is never fetched there. The server sends the markup for the block, the first frame (or your poster) and the loading bar. In the browser, React takes over and vidscroll starts downloading the video.

With an explicit length such as "400vh", the server already sends the block at its full height, so nothing on the page moves when the video starts.

Next steps