vidscroll0.3.3

Section

Show content over part of the video, by scroll position, time or frame.

A Section goes inside a ScrollVideo. It’s active while the scroll is inside its range: by default it fades in, sits over the video, and fades out again when you leave the range. Everything you put inside it is ordinary React content.

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 fromTime={12} toTime={18}>
        <h2>Open water</h2>
      </Section>
    </ScrollVideo>
  );
}

Ranges

A range has a start and an end, and each can be given in one of three units:

PropsUnitUse it when
start, endScroll progress, 0 to 1The text belongs to a stretch of scrolling, like the first third of the block.
fromTime, toTimeSeconds of videoThe text belongs to a scene, like a caption for 12 s to 18 s.
fromFrame, toFrameFrame numbersYou’re matching exact frames from an editing tool.

The difference matters when you use easing. Progress ranges are positions in the scroll, so easing doesn’t move them. Time and frame ranges are tied to the video, so they stay on their scenes whatever the easing curve does to the scroll.

Some details:

  • A missing start means the beginning, a missing end means the end. A section with no range at all is active the whole time.
  • The start and end are resolved separately, so you can mix them, for example start={0.2} with toTime={18}. If both kinds are given for one end, progress wins over time, and time over frames.
  • A section is active from its start up to, but not including, its end. A section that ends at the very end of the video stays active there.
  • Time and frame ranges only become active once the video’s duration is known, so they never flash on while it loads.
  • Frame numbers use the fps prop (30 by default) unless the browser measures the video’s real frame rate.

Following the progress with CSS

Every section sets a --progress CSS variable on itself: 0 before its range, rising to 1 across it, and 1 after it. Children can use it for any effect, with no JavaScript and no re-renders:

Scroll inside the preview
Example.tsx
import { ScrollVideo, Section } from "vidscroll";

export default function Example() {
  return (
    <ScrollVideo src="/train.mp4" length="400vh">
      <Section id="caption" start={0.1} end={0.9}>
        <div style={{ textAlign: "center" }}>
          <div style={{ width: 240, height: 4, margin: "0 auto 32px", background: "rgb(255 255 255 / 0.25)" }}>
            <div style={{ width: "calc(var(--progress) * 100%)", height: "100%", background: "#f6b98e" }} />
          </div>
          <h2 style={{ transform: "translateY(calc((1 - var(--progress)) * 80px))" }}>
            Rising with the scroll
          </h2>
        </div>
      </Section>
    </ScrollVideo>
  );
}

The bar fills with width: calc(var(--progress) * 100%) and the heading rises into place as --progress goes from 0 to 1. In your own CSS, the heading part looks like this:

styles.css
.caption h2 {
  transform: translateY(calc((1 - var(--progress)) * 80px));
}

Styling active sections

By default a section covers the video, centres its content, and fades over 0.4 s. While active it has a data-active attribute, so you can restyle it with CSS:

styles.css
.caption {
  align-items: flex-end;
  padding-bottom: 12vh;
}

.caption[data-active] h2 {
  letter-spacing: 0.02em;
}
Hero.tsx
<Section className="caption" fromTime={12} toTime={18}>
  <h2>Open water</h2>
</Section>

activeClassName and inactiveClassName add a class in either state instead, which helps with utility CSS such as Tailwind. All the default styles have zero specificity, so any rule you write wins.

Section ids

Each section gets an id, used as its data-vidscroll-section attribute and in events. An automatic one is generated, so you only need id when you want to refer to the section from code, for example with api.getSectionProgress("intro") or to tell sections apart in sectionEnter events. Ids must be unique within one ScrollVideo.

Props

PropTypeDefaultDescription
startnumber0Start of the range in scroll progress, 0 to 1.
endnumber1End of the range in scroll progress, 0 to 1.
fromTimenumbernoneStart of the range in seconds.
toTimenumbernoneEnd of the range in seconds.
fromFramenumbernoneStart of the range as a frame number.
toFramenumbernoneEnd of the range as a frame number.
idstringautomaticName for events and getSectionProgress. Unique within the ScrollVideo.
asElementType"div"Element or component to render, such as "section".
classNamestringnoneClass on the section element.
activeClassNamestringnoneExtra class while active.
inactiveClassNamestringnoneExtra class while inactive.
styleCSSPropertiesnoneInline styles on the section element.
dataunknownnoneStored with the section. Not passed to events or hooks yet, so it has no visible effect today.
  • ScrollVideo: length, easing and loading.
  • Hooks: react to active sections from your own components.