npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

scroll-cinematic

v1.1.3

Published

Scroll-driven cinematic frame sequence animations for React and Next.js

Readme

scroll-cinematic

Scroll-driven frame sequence animation for React & Next.js.
One scroll. 169 frames. Zero compromise.

npm version bundle size license zero deps TypeScript

→ Live Demo  ·  npm  ·  Examples

You've seen it on Apple product pages. On Stripe. On those insane award-winning portfolios — a video that plays as you scroll. Not actually a video. A canvas, rendering a sequence of pre-extracted frames, GPU-accelerated, frame-perfect, synced to your scroll position to the pixel.

scroll-cinematic does this in one component.


[!NOTE] v1.1.2 Update: This release introduces major performance and visual enhancements to scroll-cinematic. It features a new progressive LOD (Level of Detail) preloader that loads keyframes first for a 4x faster page-load time, letting users scroll immediately while detail frames buffer in the background. Additionally, we’ve added butter-smooth frame interpolation (lerp-easing) to eliminate visual stutter during rapid scrolls, a DPR cap of 2.0 to prevent GPU memory bloat on Retina/4K screens, and scroll-time layout caching to completely eliminate layout thrashing.

v1.1.3 Update: This release fixes a critical mobile usability issue where the full-screen canvas captured touch events and blocked page scrolling. By setting pointerEvents: "none" on the canvas, touches now pass through naturally, allowing smooth mobile swiping.

Why Not Just Use <video>?

<video currentTime = scrollY> desynchs at unpredictable intervals because video decoding is not frame-addressable in the browser. Canvas drawImage with preloaded Image objects is. That's the whole insight.

| | scroll-cinematic | <video> scrubbing | GSAP setup | |---|:---:|:---:|:---:| | Frame-perfect scroll sync | ✅ | ❌ | ⚠️ | | Instant reverse playback | ✅ | ❌ | ✅ | | Deterministic rendering | ✅ | ❌ | ✅ | | React-native | ✅ | ❌ | ⚠️ | | SSR safe | ✅ | ⚠️ | ⚠️ | | Zero dependencies | ✅ | ✅ | ❌ | | Overlay composition | ✅ | ❌ | ❌ | | One component | ✅ | ❌ | ❌ |


Install

npm install scroll-cinematic
# or
yarn add scroll-cinematic
# or
pnpm add scroll-cinematic

Peer dependency: React ≥ 18. That's it. No GSAP. No three.js. No baggage.


Quick Start

import { ScrollSequence } from "scroll-cinematic";

export default function Hero() {
  return (
    <ScrollSequence
      frames="/frames/frame_%04d.webp"  // → frame_0001.webp, frame_0002.webp ...
      frameCount={169}
      height="300vh"
      title="Something Worth Watching"
      subtitle="Scroll to begin"
      dialogues={[
        {
          id: "act-1",
          show: 0.1,
          hide: 0.35,
          title: "01 — The Beginning",
          text: "Every frame is a decision.",
          author: "You",
          source: "YOUR SITE — 2026",
        },
      ]}
    />
  );
}

That's a full cinematic scroll section — sticky canvas, frame preloader, progress bar, overlay text — all from one component.


How It Works

USER SCROLLS DOWN
       │
       ▼
  scrollY  ──►  progress (0→1)
  progress ──►  frameIndex (0→N)
  frameIndex ──► canvas.drawImage(frame)
                          ▲
          GPU-accelerated 2D canvas
       │
       ▼
  60fps. Smooth. Cinematic.

API

<ScrollSequence />

Required

| Prop | Type | Description | |---|---|---| | frames | string \| (i: number) => string | Frame path template. Supports %d, %03d, %04d. Or pass a function. | | frameCount | number | Total frames in your sequence. |

Layout & Scroll

| Prop | Type | Default | Description | |---|---|---|---| | height | string | "300vh" | Scroll container height. Longer = slower playback. | | stickyCanvas | boolean | true | Sticky-in-container vs. position: fixed. | | mobileZoom | number | 1.3 | Scale multiplier on screens ≤ 768px. |

Overlay Text

| Prop | Type | Description | |---|---|---| | title | string | Hero title text. | | subtitle | string | Subtitle below the title. | | eyebrow | string | Monospace badge above the title. | | textFadeEnd | number | Scroll progress at which title fades out. Default: 0.08. | | dialogues | Dialogue[] | Timed subtitle/quote blocks. See below. |

Loading & Progress

| Prop | Type | Default | Description | |---|---|---|---| | showLoadingProgress | boolean | true | Top bar while frames preload. | | loadingText | string | "Loading frames..." | Text beside the loader. | | showProgressBar | boolean | true | Bottom scroll indicator. |

Callbacks & Classes

| Prop | Type | Description | |---|---|---| | onProgress | (progress: number, frameIndex: number) => void | Fires every scroll update. | | className | string | Class on the section wrapper. | | canvasClassName | string | Class on the <canvas> element. | | overlayClassName | string | Class on the overlay container. |

Dialogue Object

type Dialogue = {
  id: string;        // Unique key
  show: number;      // Progress value (0–1) to appear
  hide: number;      // Progress value (0–1) to disappear
  title?: string;    // Bold heading
  text: string;      // Body copy
  author?: string;   // Attribution line
  source?: string;   // Source label (monospace style)
};

Headless Hook

For full control — build your own canvas, your own overlays, your own everything.

import { useScrollFrames } from "scroll-cinematic";

function MyCustomSequence() {
  const {
    containerRef,    // → attach to the <section> scroll container
    canvasRef,       // → attach to your <canvas>
    loaded,          // boolean — all frames preloaded
    loadProgress,    // 0–1 preload progress
    scrollProgress,  // 0–1 scroll position
    frameIndex,      // current frame number
  } = useScrollFrames({
    frames: "/frames/frame_%04d.webp",
    frameCount: 169,
    mobileZoom: 1.3,
  });

  return (
    <section ref={containerRef} style={{ height: "400vh", position: "relative" }}>
      <canvas
        ref={canvasRef}
        style={{ position: "sticky", top: 0, width: "100%", height: "100vh" }}
      />
      <div className="my-overlay">
        {Math.round(scrollProgress * 100)}% through
      </div>
    </section>
  );
}

Prepare Your Frames

# Step 1 — extract frames at 30fps
ffmpeg -i video.mp4 -vf fps=30/1 frames/frame_%04d.jpg

# Step 2 — compress to WebP (cuts size 50–70%)
cwebp frames/*.jpg -folder frames_webp

# Step 3 — serve from /public/frames/

Your output:

frames/
  frame_0001.webp  (~30kb)
  frame_0002.webp  (~30kb)
  ...
  frame_0169.webp  (~30kb)

Rule of thumb: 1 frame per ~2px of scroll height.
height="300vh" at a 1080p display ≈ 3240px ≈ 162 frames at 30fps.


Performance

| What makes it fast | |---| | Canvas 2D — no DOM mutations on every frame | | All frames preloaded into Image() objects in memory | | RAF only runs when section is in viewport (IntersectionObserver) | | Cover-fit scaling computed once, not per-frame | | WebP frames ≈ 30–50kb each vs 80–150kb JPG |


SSR / Next.js

Works out of the box. useScrollFrames guards all window/document access behind mount checks. Drop it into any App Router page:

// app/page.tsx — works as-is
import { ScrollSequence } from "scroll-cinematic";

export default function Page() {
  return <ScrollSequence frames="..." frameCount={169} />;
}

Architecture

scroll-cinematic/
│
├─ <ScrollSequence />          ← Drop-in component. You probably want this.
│     │
│     ├─ useScrollFrames()     ← The engine. Handles everything below.
│     │     │
│     │     ├─ Preloader       ← Fetches all N frames in parallel, fires onProgress
│     │     ├─ IntersectionObs ← Starts/stops RAF only when visible
│     │     ├─ ScrollTracker   ← Maps scrollY → progress (0–1)
│     │     └─ Canvas Renderer ← drawImage() with cover-fit scaling + mobile zoom
│     │
│     └─ Overlay Layer         ← Title, subtitle, eyebrow, Dialogue[] timed blocks
│
└─ useScrollFrames()           ← Headless hook for custom layouts

Repo layout

scroll-cinematic/
├── src/
│   ├── ScrollSequence.tsx       ← Main component
│   ├── useScrollFrames.ts       ← Core hook
│   └── preloader.ts             ← Frame preloading logic
├── dist/                        ← Built ESM + CJS output
└── template-cinematic-website/  ← Full demo: Next.js + Tailwind + Framer Motion + Lenis
npm run build   # → dist/
npm run dev     # → watch mode

Use Cases

  • Product reveals — show a physical product rotating or assembling as the user scrolls
  • Startup landing pages — turn your hero section into a moment people remember
  • Agency & studio portfolios — demonstrate craft before the client reads a word
  • Film & media sites — let trailers breathe through a scene frame by frame
  • Interactive storytelling — pair dialogues with action beats to build narrative
  • Brand campaigns — the format that used to require a full production team

License

MIT © 2026