Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details →
Socket
Book a DemoInstallSign in
Socket

react-scroll-media

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

react-scroll-media - npm Package Compare versions

Comparing version
1.0.0
to
1.0.1
+1
-1
package.json
{
"name": "react-scroll-media",
"version": "1.0.0",
"version": "1.0.1",
"description": "Production-ready scroll-driven image sequence rendering component for React",

@@ -5,0 +5,0 @@ "license": "MIT",

+441
-236

@@ -1,70 +0,100 @@

# React Scroll Media šŸŽ¬
<div align="center">
> **Production-ready, cinematic scroll sequences for React.**
> Zero scroll-jacking. Pure sticky positioning. 60fps performance.
# šŸŽ¬ React Scroll Media
`react-scroll-media` is a lightweight library for creating Apple-style "scrollytelling" image sequences. It maps scroll progress to image frames deterministically, using standard CSS sticky positioning for a native, jank-free feel.
**Production-ready, cinematic scroll sequences for React.**
## ✨ Features
[![npm version](https://img.shields.io/npm/v/react-scroll-media.svg)](https://www.npmjs.com/package/react-scroll-media)
[![npm downloads](https://img.shields.io/npm/dm/react-scroll-media.svg)](https://www.npmjs.com/package/react-scroll-media)
[![bundle size](https://img.shields.io/bundlephobia/minzip/react-scroll-media)](https://bundlephobia.com/package/react-scroll-media)
[![license](https://img.shields.io/npm/l/react-scroll-media.svg)](https://github.com/yourusername/react-scroll-media/blob/main/LICENSE)
- **šŸš€ Native Performance**:
- Uses `requestAnimationFrame` for buttery smooth 60fps rendering.
- **No Scroll Jacking**: We never hijack the scrollbar. It works with native scrolling.
- **CSS Sticky**: Uses relatively positioned containers with sticky inner content.
- **šŸ–¼ļø Flexible Loading**:
- **Manual**: Pass an array of image URLs.
- **Pattern**: Generate sequences like `/img_{index}.jpg`.
- **Manifest**: Load sequences from a JSON manifest.
- **🧠 Smart Memory Management**:
- **Lazy Mode**: Keeps only ±3 frames in memory for huge sequences (800+ frames).
- **Eager Mode**: Preloads everything for maximum smoothness on smaller sequences.
- **Decoding**: Uses `img.decode()` to prevent main-thread jank during painting.
- **šŸ› ļø Developer Experience**:
- **Debug Overlay**: Visualize progress and frame index in real-time.
- **Hooks**: Exported `useScrollSequence` for custom UI implementations.
- **TypeScript**: First-class type definitions.
- **SSR Safe**: Works perfectly with Next.js / Remix / Gatsby.
- **A11y**: Built-in support for `prefers-reduced-motion` and ARIA attributes.
- **Robust**: Error boundaries and callbacks for image load failures.
*Zero scroll-jacking • Pure sticky positioning • 60fps performance*
## šŸ¤” When to use this vs Video?
[Installation](#-installation) • [Usage](#-usage) • [API](#%EF%B8%8F-configuration) • [Examples](#-usage)
Feature
</div>
Video (`<video>`)
---
Scroll Sequence (`react-scroll-media`)
## 🌟 Overview
**Quality**
`react-scroll-media` is a lightweight library for creating Apple-style "scrollytelling" image sequences. It maps scroll progress to image frames deterministically, using standard CSS sticky positioning for a native, jank-free feel.
Compressed (artifacts)
<br />
Lossless / Exact Frames (CRISP)
## ✨ Features
**Transparency**
### šŸš€ **Native Performance**
- Uses `requestAnimationFrame` for buttery smooth 60fps rendering
- **No Scroll Jacking** — We never hijack the scrollbar. It works with native scrolling
- **CSS Sticky** — Uses relatively positioned containers with sticky inner content
Difficult (needs webm/hevc)
### šŸ–¼ļø **Flexible Loading**
- **Manual** — Pass an array of image URLs
- **Pattern** — Generate sequences like `/img_{index}.jpg`
- **Manifest** — Load sequences from a JSON manifest
Native PNG/WebP Transparency (Easy)
### 🧠 **Smart Memory Management**
- **Lazy Mode** — Keeps only ±3 frames in memory for huge sequences (800+ frames)
- **Eager Mode** — Preloads everything for maximum smoothness on smaller sequences
- **Decoding** — Uses `img.decode()` to prevent main-thread jank during painting
**Scrubbing**
### šŸ› ļø **Developer Experience**
- **Debug Overlay** — Visualize progress and frame index in real-time
- **Hooks** — Exported `useScrollSequence` for custom UI implementations
- **TypeScript** — First-class type definitions
- **SSR Safe** — Works perfectly with Next.js / Remix / Gatsby
- **A11y** — Built-in support for `prefers-reduced-motion` and ARIA attributes
- **Robust** — Error boundaries and callbacks for image load failures
Janky (keyframe dependency)
<br />
1:1 Instant Scrubbing
---
**Mobile**
## šŸ¤” When to Use This vs Video?
Auto-play often blocked
<table>
<thead>
<tr>
<th>Feature</th>
<th>Video (<code>&lt;video&gt;</code>)</th>
<th>Scroll Sequence (<code>react-scroll-media</code>)</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Quality</strong></td>
<td>Compressed (artifacts)</td>
<td>✨ Lossless / Exact Frames (CRISP)</td>
</tr>
<tr>
<td><strong>Transparency</strong></td>
<td>Difficult (needs webm/hevc)</td>
<td>✨ Native PNG/WebP Transparency (Easy)</td>
</tr>
<tr>
<td><strong>Scrubbing</strong></td>
<td>Janky (keyframe dependency)</td>
<td>✨ 1:1 Instant Scrubbing</td>
</tr>
<tr>
<td><strong>Mobile</strong></td>
<td>Auto-play often blocked</td>
<td>✨ Works everywhere</td>
</tr>
<tr>
<td><strong>File Size</strong></td>
<td>✨ Small</td>
<td>Large (requires optimization/lazy loading)</td>
</tr>
</tbody>
</table>
Works everywhere
**šŸ’” Use Scroll Sequence** when you need perfect interaction, transparency, or crystal-clear product visuals (like Apple).
**File Size**
**šŸ’” Use Video** for long, non-interactive backgrounds.
Small
<br />
Large (requires optimization/lazy loading)
Use **Scroll Sequence** when you need perfect interaction, transparency, or crystal-clear product visuals (like Apple). Use **Video** for long, non-interactive backgrounds.
---

@@ -75,5 +105,13 @@

```bash
npm install react-scroll-media# oryarn add react-scroll-media
npm install react-scroll-media
```
**or**
```bash
yarn add react-scroll-media
```
<br />
---

@@ -83,3 +121,3 @@

### Basic Example
### šŸŽÆ Basic Example

@@ -89,5 +127,28 @@ The simplest way to use it is with the `ScrollSequence` component.

```tsx
import { ScrollSequence } from 'react-scroll-media';const frames = [ '/images/frame_01.jpg', '/images/frame_02.jpg', // ...];export default function MyPage() { return ( <div style={{ height: '200vh' }}> <h1>Scroll Down</h1> <ScrollSequence source={{ type: 'manual', frames }} scrollLength="300vh" // Determines how long the sequence plays /> <h1>Continue Scrolling</h1> </div> );}
import { ScrollSequence } from 'react-scroll-media';
const frames = [
'/images/frame_01.jpg',
'/images/frame_02.jpg',
// ...
];
export default function MyPage() {
return (
<div style={{ height: '200vh' }}>
<h1>Scroll Down</h1>
<ScrollSequence
source={{ type: 'manual', frames }}
scrollLength="300vh" // Determines how long the sequence plays
/>
<h1>Continue Scrolling</h1>
</div>
);
}
```
<br />
### ✨ Scrollytelling & Composition

@@ -97,22 +158,53 @@

#### Animated Text (`ScrollText`)
<br />
#### šŸ“ Animated Text (`ScrollText`)
Animate opacity and position based on scroll progress (0 to 1). Supports enter and exit phases.
```tsx
import { ScrollSequence, ScrollText } from 'react-scroll-media';<ScrollSequence source={...} scrollLength="400vh"> {/* Fade In (0.1-0.2) -> Hold -> Fade Out (0.8-0.9) */} <ScrollText start={0.1} end={0.2} exitStart={0.8} exitEnd={0.9} translateY={50} className="my-text-overlay" > Cinematic Experience </ScrollText></ScrollSequence>
import { ScrollSequence, ScrollText } from 'react-scroll-media';
<ScrollSequence source={...} scrollLength="400vh">
{/* Fade In (0.1-0.2) -> Hold -> Fade Out (0.8-0.9) */}
<ScrollText
start={0.1}
end={0.2}
exitStart={0.8}
exitEnd={0.9}
translateY={50}
className="my-text-overlay"
>
Cinematic Experience
</ScrollText>
</ScrollSequence>
```
#### Word Reveal (`ScrollWordReveal`)
<br />
#### šŸ’¬ Word Reveal (`ScrollWordReveal`)
Reveals text word-by-word as you scroll.
```tsx
import { ScrollWordReveal } from 'react-scroll-media';<ScrollWordReveal text="Experience the smooth cinematic scroll." start={0.4} end={0.6} style={{ fontSize: '2rem', color: 'white' }}/>
import { ScrollWordReveal } from 'react-scroll-media';
<ScrollWordReveal
text="Experience the smooth cinematic scroll."
start={0.4}
end={0.6}
style={{ fontSize: '2rem', color: 'white' }}
/>
```
### Advanced: Custom Hooks
<br />
### šŸ”§ Advanced: Custom Hooks
For full control over the specialized UI, use the headless hooks.
<br />
#### `useScrollSequence`

@@ -123,5 +215,13 @@

```tsx
import { useScrollSequence } from 'react-scroll-media';const CustomScroller = () => { // ... setup refs const { containerRef, canvasRef, isLoaded } = useScrollSequence({ ... }); // ... render custom structure};
import { useScrollSequence } from 'react-scroll-media';
const CustomScroller = () => {
// ... setup refs
const { containerRef, canvasRef, isLoaded } = useScrollSequence({ ... });
// ... render custom structure
};
```
<br />
#### `useScrollTimeline`

@@ -132,5 +232,18 @@

```tsx
import { useScrollTimeline } from 'react-scroll-media';const MyComponent = () => { const { subscribe } = useScrollTimeline(); // Subscribe to progress (0-1) useEffect(() => subscribe((progress) => { console.log('Progress:', progress); }), [subscribe]); return <div>...</div>;};
import { useScrollTimeline } from 'react-scroll-media';
const MyComponent = () => {
const { subscribe } = useScrollTimeline();
// Subscribe to progress (0-1)
useEffect(() => subscribe((progress) => {
console.log('Progress:', progress);
}), [subscribe]);
return <div>...</div>;
};
```
<br />
---

@@ -142,179 +255,215 @@

Prop
<table>
<thead>
<tr>
<th>Prop</th>
<th>Type</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>source</code></td>
<td><code>SequenceSource</code></td>
<td><strong>Required</strong></td>
<td>Defines where images come from.</td>
</tr>
<tr>
<td><code>scrollLength</code></td>
<td><code>string</code></td>
<td><code>"300vh"</code></td>
<td>Height of the container (animation duration).</td>
</tr>
<tr>
<td><code>memoryStrategy</code></td>
<td><code>"eager" | "lazy"</code></td>
<td><code>"eager"</code></td>
<td>Optimization strategy.</td>
</tr>
<tr>
<td><code>lazyBuffer</code></td>
<td><code>number</code></td>
<td><code>10</code></td>
<td>Number of frames to keep loaded in lazy mode.</td>
</tr>
<tr>
<td><code>fallback</code></td>
<td><code>ReactNode</code></td>
<td><code>null</code></td>
<td>Loading state component.</td>
</tr>
<tr>
<td><code>accessibilityLabel</code></td>
<td><code>string</code></td>
<td><code>"Scroll sequence"</code></td>
<td>ARIA label for the canvas. Example: <code>"360 degree view of the product"</code>.</td>
</tr>
<tr>
<td><code>debug</code></td>
<td><code>boolean</code></td>
<td><code>false</code></td>
<td>Shows debug overlay.</td>
</tr>
<tr>
<td><code>onError</code></td>
<td><code>(error: Error) => void</code></td>
<td><code>undefined</code></td>
<td>Callback fired when an image fails to load or initialization errors occur.</td>
</tr>
</tbody>
</table>
Type
<br />
Default
---
Description
## šŸ“Š Performance & Compatibility
`source`
### šŸ“¦ Bundle Size
`SequenceSource`
| Metric | Size |
|--------|------|
| **Minified** | ~22.0 kB |
| **Gzipped** | ~6.08 kB |
**Required**
✨ **Zero dependencies** — Uses native Canvas API, no heavyweight libraries.
Defines where images come from.
<br />
`scrollLength`
### 🌐 Browser Support
`string`
<table>
<thead>
<tr>
<th>Browser</th>
<th>Status</th>
<th>Note</th>
</tr>
</thead>
<tbody>
<tr>
<td>Chrome</td>
<td>āœ…</td>
<td>Full support (OffscreenCanvas enabled)</td>
</tr>
<tr>
<td>Firefox</td>
<td>āœ…</td>
<td>Full support</td>
</tr>
<tr>
<td>Safari</td>
<td>āœ…</td>
<td>Full support (Desktop & Mobile)</td>
</tr>
<tr>
<td>Edge</td>
<td>āœ…</td>
<td>Full support</td>
</tr>
<tr>
<td>IE11</td>
<td>āŒ</td>
<td>Not supported (Missing ES6/Canvas features)</td>
</tr>
</tbody>
</table>
`"300vh"`
<br />
Height of the container (animation duration).
### ♿ Accessibility (A11y)
`memoryStrategy`
- **šŸŽ¹ Keyboard Navigation** — Users can scrub through the sequence using standard keyboard controls (Arrow Keys, Spacebar, Page Up/Down) because it relies on native scrolling.
`"eager" | "lazy"`
- **šŸ”Š Screen Readers** — Add `accessibilityLabel` to `ScrollSequence` to provide a description for the canvas. Canvas has `role="img"`.
`"eager"`
- **šŸŽ­ Reduced Motion** — Automatically detects `prefers-reduced-motion: reduce`. If enabled, `ScrollSequence` will disable the scroll animation and display the `fallback` content (if provided) or simply freeze the first frame to prevent motion sickness.
Optimization strategy.
<br />
`lazyBuffer`
### šŸ’¾ Memory Usage (Benchmarks)
`number`
*Tested on 1080p frames.*
`10`
<table>
<thead>
<tr>
<th>Frames</th>
<th>Strategy</th>
<th>Memory</th>
<th>Recommendation</th>
</tr>
</thead>
<tbody>
<tr>
<td>100</td>
<td><code>eager</code></td>
<td>30MB</td>
<td>Instant seeking, smooth.</td>
</tr>
<tr>
<td>500</td>
<td><code>eager</code></td>
<td>46MB</td>
<td>High RAM usage.</td>
</tr>
<tr>
<td>1000</td>
<td><code>eager</code></td>
<td>57MB</td>
<td>Very high RAM usage.</td>
</tr>
<tr>
<td>100</td>
<td><code>lazy</code></td>
<td>25MB</td>
<td>Low memory usage.</td>
</tr>
<tr>
<td>500</td>
<td><code>lazy</code></td>
<td>30MB</td>
<td>Low memory usage.</td>
</tr>
<tr>
<td>1000</td>
<td><code>lazy</code></td>
<td>45MB</td>
<td><strong>⭐ Recommended</strong>. Kept flat constant.</td>
</tr>
</tbody>
</table>
Number of frames to keep loaded in lazy mode.
<br />
`fallback`
### šŸ›”ļø Error Handling & Fallbacks
`ReactNode`
`null`
Loading state component.
`accessibilityLabel`
`string`
`"Scroll sequence"`
ARIA label for the canvas. Example: `"360 degree view of the product"`.
`debug`
`boolean`
`false`
Shows debug overlay.
`onError`
`(error: Error) => void`
`undefined`
Callback fired when an image fails to load or initialization errors occur.
## šŸ“Š Performance & compatibility
### Bundle Size
- **Minified**: ~22.0 kB
- **Gzipped**: ~6.08 kB
- Zero dependencies (uses native Canvas API, no heavyweight libraries).
### Browser Support
Browser
Status
Note
Chrome
āœ…
Full support (OffscreenCanvas enabled)
Firefox
āœ…
Full support
Safari
āœ…
Full support (Desktop & Mobile)
Edge
āœ…
Full support
IE11
āŒ
Not supported (Missing ES6/Canvas features)
### Accessibility (A11y)
- **Keyboard Navigation**: Users can scrub through the sequence using standard keyboard controls (Arrow Keys, Spacebar, Page Up/Down) because it relies on native scrolling.
- **Screen Readers**: Add `accessibilityLabel` to `ScrollSequence` to provide a description for the canvas. Canvas has `role="img"`.
- **Reduced Motion**: Automatically detects `prefers-reduced-motion: reduce`. If enabled, `ScrollSequence` will disable the scroll animation and display the `fallback` content (if provided) or simply freeze the first frame to prevent motion sickness.
### Memory Usage (Benchmarks)
Tested on 1080p frames.
Frames
Strategy
Memory
Recommendation
100
`eager`
~50MB
Instant seeking, smooth.
500
`eager`
~250MB
High RAM usage.
500+
`lazy`
~20MB
**Recommended**. Kept flat constant.
### Error Handling & Fallbacks
Network errors are handled gracefully. You can provide a fallback UI that displays while images are loading or if they fail.
```tsx
<ScrollSequence source={{ type: 'manifest', url: '/bad_url.json' }} fallback={<div className="error">Failed to load sequence</div>} onError={(e) => console.error("Sequence error:", e)}/>
<ScrollSequence
source={{ type: 'manifest', url: '/bad_url.json' }}
fallback={<div className="error">Failed to load sequence</div>}
onError={(e) => console.error("Sequence error:", e)}
/>
```
### Error Boundaries
<br />
### 🚨 Error Boundaries
For robust production apps, wrap `ScrollSequence` in an Error Boundary to catch unexpected crashes:
```tsx
```tsx
class ErrorBoundary extends React.Component<{ fallback: React.ReactNode, children: React.ReactNode }, { hasError: boolean }> {
class ErrorBoundary extends React.Component<
{ fallback: React.ReactNode, children: React.ReactNode },
{ hasError: boolean }
> {
state = { hasError: false };
static getDerivedStateFromError() { return { hasError: true }; }
static getDerivedStateFromError() {
return { hasError: true };
}
render() {

@@ -331,11 +480,15 @@ if (this.state.hasError) return this.props.fallback;

```
```
### Multi-Instance & Nested Scroll
<br />
### šŸ”„ Multi-Instance & Nested Scroll
`react-scroll-media` automatically handles multiple instances on the same page. Each instance:
1. Registers with a shared `RAF` loop (singleton) for optimal performance.
2. Calculates its own progress independently.
3. Should have a unique `scrollLength` or container setup.
1. Registers with a shared `RAF` loop (singleton) for optimal performance.
2. Calculates its own progress independently.
3. Should have a unique `scrollLength` or container setup.
<br />
---

@@ -345,49 +498,81 @@

### `SequenceSource` Options
### šŸ“‚ `SequenceSource` Options
**1. Manual Mode** (Pass array directly)
<br />
#### **1. Manual Mode** (Pass array directly)
```ts
{ type: 'manual', frames: ['/img/1.jpg', '/img/2.jpg']}
{
type: 'manual',
frames: ['/img/1.jpg', '/img/2.jpg']
}
```
**2. Pattern Mode** (Generate URLs)
<br />
#### **2. Pattern Mode** (Generate URLs)
```ts
{ type: 'pattern', url: '/assets/sequence_{index}.jpg', // {index} is replaced start: 1, // Start index end: 100, // End index pad: 4 // Zero padding (e.g. 1 -> 0001)}
{
type: 'pattern',
url: '/assets/sequence_{index}.jpg', // {index} is replaced
start: 1, // Start index
end: 100, // End index
pad: 4 // Zero padding (e.g. 1 -> 0001)
}
```
**3. Manifest Mode** (Fetch JSON)
<br />
#### **3. Manifest Mode** (Fetch JSON)
```ts
{ type: 'manifest', url: '/sequence.json' }// JSON format: { "frames": ["url1", "url2"] } OR pattern config
{
type: 'manifest',
url: '/sequence.json'
}
// JSON format: { "frames": ["url1", "url2"] } OR pattern config
```
> **Note**: Manifests are cached in memory by URL. To force a refresh, append a query param (e.g. `?v=2`).
> **šŸ’” Note**: Manifests are cached in memory by URL. To force a refresh, append a query param (e.g. `?v=2`).
<br />
---
## šŸ—ļø Architecture
## šŸŽØ How it Works (The "Sticky" Technique)
### How it Works (The "Sticky" Technique)
Unlike libraries that use `position: fixed` or JS-based scroll locking (which breaks refreshing and feels unnatural), we use **CSS Sticky Positioning**.
1. **Container (`relative`)**: This element has the height you specify (e.g., `300vh`). It occupies space in the document flow.
2. **Sticky Wrapper (`sticky`)**: Inside the container, we place a `div` that is `100vh` tall and `sticky` at `top: 0`.
3. **Canvas**: The `<canvas>` sits inside the sticky wrapper.
4. **Math**: As you scroll the container, the sticky wrapper stays pinned to the viewport. We calculate:
```ts
progress = -containerRect.top / (containerHeight - viewportHeight)
```
This gives a precise 0.0 to 1.0 value tied to the pixel position of the scrollbar.
<br />
### Memory Strategy
### šŸ”§ Technical Breakdown
- **"eager" (Default)**: Best for sequences < 200 frames. Preloads all images into `HTMLImageElement` instances. Instant seeking, smooth playback. High memory usage.
- **"lazy"**: Best for long sequences (500+ frames). Only keeps the current frame and its neighbors in memory. Saves RAM, prevents crashes.
- Buffer size defaults to ±10 frames but can be customized via `lazyBuffer`.
1. **Container (`relative`)** — This element has the height you specify (e.g., `300vh`). It occupies space in the document flow.
2. **Sticky Wrapper (`sticky`)** — Inside the container, we place a `div` that is `100vh` tall and `sticky` at `top: 0`.
3. **Canvas** — The `<canvas>` sits inside the sticky wrapper.
4. **Math** — As you scroll the container, the sticky wrapper stays pinned to the viewport. We calculate:
```ts
progress = -containerRect.top / (containerHeight - viewportHeight)
```
This gives a precise **0.0 to 1.0** value tied to the pixel position of the scrollbar.
<br />
### šŸ’” Memory Strategy
- **"eager" (Default)** — Best for sequences < 200 frames. Preloads all images into `HTMLImageElement` instances. Instant seeking, smooth playback. High memory usage.
- **"lazy"** — Best for long sequences (500+ frames). Only keeps the current frame and its neighbors in memory. Saves RAM, prevents crashes.
- Buffer size defaults to ±10 frames but can be customized via `lazyBuffer`.
<br />
---

@@ -400,15 +585,35 @@

```tsx
<ScrollSequence source={...} debug={true} />
<ScrollSequence
source={...}
debug={true}
/>
```
<br />
**Output:**
```
Progress: 0.45Frame: 45 / 100
Progress: 0.45
Frame: 45 / 100
```
This overlay is updated directly via DOM manipulation (bypassing React renders) for zero overhead.
This overlay is updated directly via DOM manipulation (bypassing React renders) for **zero overhead**.
<br />
---
MIT Ā© 2026 Thanniru Sai Teja
<div align="center">
## šŸ“„ License
**MIT** Ā© 2026 **Thanniru Sai Teja**
<br />
Made with ā¤ļø for the React community
[⬆ Back to Top](#-react-scroll-media)
</div>