Hooks
Read the video's progress, time and sections from your own components, or control the scroll.
Three hooks connect your components to a scroll video. They work in any
component rendered inside ScrollVideo, at any depth.
| Hook | Gives you | Re-renders | Use it for |
|---|---|---|---|
useScrollVideoState() | Progress, time, frame, active sections | When they change | Small readouts: a timecode, an active chapter name |
useScrollVideoUpdate(fn) | The same, every frame, in a callback | Never | Anything animated: write to a ref |
useScrollVideo() | The api object | When the api becomes available | Actions: jump to a time, read a section’s progress |
All three are used in this example: a timecode, a bar along the bottom, and buttons that jump to a moment.
import { useRef } from "react";
import { ScrollVideo, useScrollVideo, useScrollVideoState, useScrollVideoUpdate } from "vidscroll";
function Timecode() {
const { time, ready } = useScrollVideoState();
return <p style={{ margin: 0, font: "500 32px monospace" }}>{ready ? time.toFixed(1) : "–"} s</p>;
}
function ProgressBar() {
const bar = useRef<HTMLDivElement>(null);
useScrollVideoUpdate(({ linearProgress }) => {
if (bar.current) bar.current.style.transform = `scaleX(${linearProgress})`;
});
return (
<div
ref={bar}
style={{ position: "absolute", left: 0, bottom: 0, width: "100%", height: 4, background: "#f6b98e", transformOrigin: "left" }}
/>
);
}
function Chapters() {
const { api } = useScrollVideo();
return (
<div style={{ display: "flex", gap: 8, pointerEvents: "auto" }}>
<button onClick={() => api?.scrollToTime(1)}>Doors</button>
<button onClick={() => api?.scrollToTime(13)}>Open water</button>
<button onClick={() => api?.scrollToTime(25)}>Away</button>
</div>
);
}
export default function Example() {
return (
<ScrollVideo src="/train.mp4" length="400vh">
<div style={{ position: "absolute", top: 16, left: 16, display: "grid", gap: 12, color: "#fff" }}>
<Timecode />
<Chapters />
</div>
<ProgressBar />
</ScrollVideo>
);
}useScrollVideoState
Returns the current state and re-renders the component when it changes:
import { useScrollVideoState } from "vidscroll";
export function Timecode() {
const { ready, time } = useScrollVideoState();
return <span>{ready ? time.toFixed(1) : "–"} s</span>;
}| Field | Type | Meaning |
|---|---|---|
ready | boolean | The video is loaded and following the scroll |
progress | number | Smoothed scroll progress through the block, 0 to 1 |
time | number | Video time in seconds |
frame | number | Frame number |
activeSections | string[] | Ids of the sections active right now |
While someone scrolls, these values change every frame, so the component
re-renders every frame. That’s fine for a few small components. For anything
heavier, or anything you animate, use useScrollVideoUpdate.
useScrollVideoUpdate
Calls your function on every frame with the latest state, without re-rendering. Write the result straight to the DOM through a ref:
import { useRef } from "react";
import { useScrollVideoUpdate } from "vidscroll";
export function ProgressBar() {
const bar = useRef<HTMLDivElement>(null);
useScrollVideoUpdate(({ linearProgress }) => {
if (bar.current) bar.current.style.transform = `scaleX(${linearProgress})`;
});
return <div ref={bar} className="progress-bar" />;
}The callback receives linearProgress, time, frameIndex, totalFrames,
duration and activeSections. It’s also called once right away with the
current state. The latest version of your function is always used, so it can
read props and state without being recreated.
useScrollVideo
Returns { api }. The api is null until the video component has mounted,
so check for it before using it:
import { useScrollVideo } from "vidscroll";
export function Chapters() {
const { api } = useScrollVideo();
return (
<nav style={{ pointerEvents: "auto" }}>
<button onClick={() => api?.scrollToTime(1)}>Doors</button>
<button onClick={() => api?.scrollToTime(13)}>Open water</button>
</nav>
);
}| Method | What it does |
|---|---|
scrollToTime(seconds, options?) | Scrolls the page to the point where the video shows that time. Smooth by default; pass { behavior: "instant" } to jump. Does nothing until the duration is known. |
scrollToProgress(p, options?) | Scrolls to a progress between 0 and 1, the same way. |
getSectionProgress(id) | The same 0 to 1 value as that section’s –progress. |
getState() | The current state, as passed to useScrollVideoUpdate. |
isReady() | Whether the video is loaded and following the scroll. |
on(event, fn), off(event, fn) | Subscribe to events: ready, update, sectionEnter, sectionExit and more. |
Smooth scrolling to a time scrubs the video through everything on the way,
which reads well for chapter navigation. Buttons placed over the video outside a
Section need pointer-events: auto, because the overlay ignores clicks by
default.
Outside a ScrollVideo
Used outside a ScrollVideo, the hooks don’t fail: useScrollVideo returns
{ api: null }, useScrollVideoState returns an idle state with ready: false,
and useScrollVideoUpdate never calls your function. They also work inside
ScrollFrames.
Related
- Section: ranges,
--progressand active state. - ScrollVideo: the component these hooks read from.