lazyvid
v0.1.3
Published
Lightweight lazy-loading video component with IntersectionObserver
Maintainers
Readme
lazyvid
Lazy-load <video> sources with predictable behavior.
lazyvid renders an empty <video> element and injects <source> tags only when the element enters the viewport.
Until then — nothing downloads.
It relies on IntersectionObserver for visibility tracking and lets the browser handle native format selection (webm, mp4, etc).
~2 KB (ESM bundle). Zero dependencies. Full TypeScript support.
<LazyVideo
sources={[
{ src: "/hero.webm", type: "video/webm" },
{ src: "/hero.mp4", type: "video/mp4" },
]}
muted
autoPlay
loop
/>Single component. No additional setup.
Why?
Browsers start fetching <video> sources as soon as <source> elements are present in the DOM.
On media-heavy pages, this means unnecessary bandwidth usage and background CPU activity — even if the user never scrolls to the video.
lazyvid avoids that by delaying source injection until the element becomes visible (with configurable preload offset).
- No user-agent checks.
- No format guessing.
The browser still decides which source to play.
What it does
- Renders an empty
<video> - Observes it with IntersectionObserver
- Injects
<source>elements when it enters the viewport - Optionally pauses playback when it leaves
That’s it.
Install
npm install lazyvidRequires React 18 or 19.
Usage
Basic — just lazy load it
import { LazyVideo } from "lazyvid";
<LazyVideo
sources={[
{ src: "/promo.webm", type: "video/webm" },
{ src: "/promo.mp4", type: "video/mp4" },
]}
poster="/promo-poster.jpg"
controls
/>;Sources are listed in priority order. The browser takes the first format it supports — put your lightest format first.
Background video that pauses off-screen
<LazyVideo
sources={[
{ src: "/bg.webm", type: "video/webm" },
{ src: "/bg.mp4", type: "video/mp4" },
]}
poster="/bg-poster.jpg"
autoPlay
muted
loop
pauseOnLeave
style={{ width: "100%", objectFit: "cover" }}
/>When the user scrolls away — video pauses. Scrolls back — resumes. No wasted CPU on invisible playback.
onLoaded — trigger actions when video enters viewport
onLoaded fires once, when the video enters the viewport, <source> elements are injected, and loading starts. It does not fire when loading is complete — use onCanPlay for that.
This allows you to build logic on top of it, for example:
- start animations or UI effects
- show captions or overlays
- preload next section, next video, or related data
- track engagement: the user reached this video
<div style={{ position: "relative" }}>
<LazyVideo
sources={[{ src: "/intro.mp4", type: "video/mp4" }]}
poster="/intro-thumb.jpg"
controls
onLoaded={() => {
console.log("Video entered viewport, loading started!");
// trigger animations, preload next content, track engagement...
}}
/>
</div>Start loading earlier
By default, loading starts 200px before the video is visible. Want more buffer?
<LazyVideo
sources={[{ src: "/hero.mp4", type: "video/mp4" }]}
rootMargin="500px"
muted
autoPlay
/>Props
| Prop | Type | Default | Description |
| -------------- | --------------------- | --------- | ----------------------------------------------- |
| sources | VideoSource[] | required | { src, type } objects in priority order |
| threshold | number | 0 | How much of the element should be visible (0–1) |
| rootMargin | string | "200px" | Start loading before the element is in view |
| pauseOnLeave | boolean | false | Pause when out of viewport, resume when back |
| onLoaded | () => void | — | Fires when sources are injected |
| ...rest | VideoHTMLAttributes | — | Any native <video> attribute works |
How it works
- Renders an empty
<video>— nothing downloads - IntersectionObserver watches it (with
rootMarginfor early preloading) - Element enters viewport →
<source>tags injected → browser picks best format → loading starts - Observer disconnects (one-time job)
- If
pauseOnLeaveis on, a second observer manages play/pause on scroll
Types
import { LazyVideo, type VideoSource } from "lazyvid";License
MIT
