react-scroll-media 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +463 -258
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,315 +1,464 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
> Zero scroll-jacking. Pure sticky positioning. 60fps performance.
|
|
3
|
+
# 🎬 React Scroll Media
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
## ✨ Features
|
|
9
|
-
|
|
10
|
-
- **🚀 Native Performance**:
|
|
11
|
-
- Uses `requestAnimationFrame` for buttery smooth 60fps rendering.
|
|
12
|
-
- **No Scroll Jacking**: We never hijack the scrollbar. It works with native scrolling.
|
|
13
|
-
- **CSS Sticky**: Uses relatively positioned containers with sticky inner content.
|
|
14
|
-
- **🖼️ Flexible Loading**:
|
|
15
|
-
- **Manual**: Pass an array of image URLs.
|
|
16
|
-
- **Pattern**: Generate sequences like `/img_{index}.jpg`.
|
|
17
|
-
- **Manifest**: Load sequences from a JSON manifest.
|
|
18
|
-
- **🧠 Smart Memory Management**:
|
|
19
|
-
- **Lazy Mode**: Keeps only ±3 frames in memory for huge sequences (800+ frames).
|
|
20
|
-
- **Eager Mode**: Preloads everything for maximum smoothness on smaller sequences.
|
|
21
|
-
- **Decoding**: Uses `img.decode()` to prevent main-thread jank during painting.
|
|
22
|
-
- **🛠️ Developer Experience**:
|
|
23
|
-
- **Debug Overlay**: Visualize progress and frame index in real-time.
|
|
24
|
-
- **Hooks**: Exported `useScrollSequence` for custom UI implementations.
|
|
25
|
-
- **TypeScript**: First-class type definitions.
|
|
26
|
-
- **SSR Safe**: Works perfectly with Next.js / Remix / Gatsby.
|
|
27
|
-
- **A11y**: Built-in support for `prefers-reduced-motion` and ARIA attributes.
|
|
28
|
-
- **Robust**: Error boundaries and callbacks for image load failures.
|
|
29
|
-
|
|
30
|
-
## 🤔 When to use this vs Video?
|
|
5
|
+
**Production-ready, cinematic scroll sequences for React.**
|
|
31
6
|
|
|
32
|
-
|
|
7
|
+
[](https://www.npmjs.com/package/react-scroll-media)
|
|
8
|
+
[](https://www.npmjs.com/package/react-scroll-media)
|
|
9
|
+
[](https://bundlephobia.com/package/react-scroll-media)
|
|
10
|
+
[](https://github.com/yourusername/react-scroll-media/blob/main/LICENSE)
|
|
33
11
|
|
|
34
|
-
|
|
12
|
+
*Zero scroll-jacking • Pure sticky positioning • 60fps performance*
|
|
35
13
|
|
|
36
|
-
|
|
14
|
+
[Installation](#-installation) • [Usage](#-usage) • [API](#%EF%B8%8F-configuration) • [Examples](#-usage)
|
|
37
15
|
|
|
38
|
-
|
|
16
|
+
</div>
|
|
39
17
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
Lossless / Exact Frames (CRISP)
|
|
43
|
-
|
|
44
|
-
**Transparency**
|
|
45
|
-
|
|
46
|
-
Difficult (needs webm/hevc)
|
|
18
|
+
---
|
|
47
19
|
|
|
48
|
-
|
|
20
|
+
## 🌟 Overview
|
|
49
21
|
|
|
50
|
-
|
|
22
|
+
`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.
|
|
51
23
|
|
|
52
|
-
|
|
24
|
+
<br />
|
|
53
25
|
|
|
54
|
-
|
|
26
|
+
## ✨ Features
|
|
55
27
|
|
|
56
|
-
**
|
|
28
|
+
### 🚀 **Native Performance**
|
|
29
|
+
- Uses `requestAnimationFrame` for buttery smooth 60fps rendering
|
|
30
|
+
- **No Scroll Jacking** — We never hijack the scrollbar. It works with native scrolling
|
|
31
|
+
- **CSS Sticky** — Uses relatively positioned containers with sticky inner content
|
|
57
32
|
|
|
58
|
-
|
|
33
|
+
### 🖼️ **Flexible Loading**
|
|
34
|
+
- **Manual** — Pass an array of image URLs
|
|
35
|
+
- **Pattern** — Generate sequences like `/img_{index}.jpg`
|
|
36
|
+
- **Manifest** — Load sequences from a JSON manifest
|
|
59
37
|
|
|
60
|
-
|
|
38
|
+
### 🧠 **Smart Memory Management**
|
|
39
|
+
- **Lazy Mode** — Keeps only ±3 frames in memory for huge sequences (800+ frames)
|
|
40
|
+
- **Eager Mode** — Preloads everything for maximum smoothness on smaller sequences
|
|
41
|
+
- **Decoding** — Uses `img.decode()` to prevent main-thread jank during painting
|
|
61
42
|
|
|
62
|
-
**
|
|
43
|
+
### 🛠️ **Developer Experience**
|
|
44
|
+
- **Debug Overlay** — Visualize progress and frame index in real-time
|
|
45
|
+
- **Hooks** — Exported `useScrollSequence` for custom UI implementations
|
|
46
|
+
- **TypeScript** — First-class type definitions
|
|
47
|
+
- **SSR Safe** — Works perfectly with Next.js / Remix / Gatsby
|
|
48
|
+
- **A11y** — Built-in support for `prefers-reduced-motion` and ARIA attributes
|
|
49
|
+
- **Robust** — Error boundaries and callbacks for image load failures
|
|
63
50
|
|
|
64
|
-
|
|
51
|
+
<br />
|
|
65
52
|
|
|
66
|
-
|
|
53
|
+
---
|
|
67
54
|
|
|
68
|
-
|
|
55
|
+
## 🤔 When to Use This vs Video?
|
|
56
|
+
|
|
57
|
+
<table>
|
|
58
|
+
<thead>
|
|
59
|
+
<tr>
|
|
60
|
+
<th>Feature</th>
|
|
61
|
+
<th>Video (<code><video></code>)</th>
|
|
62
|
+
<th>Scroll Sequence (<code>react-scroll-media</code>)</th>
|
|
63
|
+
</tr>
|
|
64
|
+
</thead>
|
|
65
|
+
<tbody>
|
|
66
|
+
<tr>
|
|
67
|
+
<td><strong>Quality</strong></td>
|
|
68
|
+
<td>Compressed (artifacts)</td>
|
|
69
|
+
<td>✨ Lossless / Exact Frames (CRISP)</td>
|
|
70
|
+
</tr>
|
|
71
|
+
<tr>
|
|
72
|
+
<td><strong>Transparency</strong></td>
|
|
73
|
+
<td>Difficult (needs webm/hevc)</td>
|
|
74
|
+
<td>✨ Native PNG/WebP Transparency (Easy)</td>
|
|
75
|
+
</tr>
|
|
76
|
+
<tr>
|
|
77
|
+
<td><strong>Scrubbing</strong></td>
|
|
78
|
+
<td>Janky (keyframe dependency)</td>
|
|
79
|
+
<td>✨ 1:1 Instant Scrubbing</td>
|
|
80
|
+
</tr>
|
|
81
|
+
<tr>
|
|
82
|
+
<td><strong>Mobile</strong></td>
|
|
83
|
+
<td>Auto-play often blocked</td>
|
|
84
|
+
<td>✨ Works everywhere</td>
|
|
85
|
+
</tr>
|
|
86
|
+
<tr>
|
|
87
|
+
<td><strong>File Size</strong></td>
|
|
88
|
+
<td>✨ Small</td>
|
|
89
|
+
<td>Large (requires optimization/lazy loading)</td>
|
|
90
|
+
</tr>
|
|
91
|
+
</tbody>
|
|
92
|
+
</table>
|
|
93
|
+
|
|
94
|
+
**💡 Use Scroll Sequence** when you need perfect interaction, transparency, or crystal-clear product visuals (like Apple).
|
|
95
|
+
|
|
96
|
+
**💡 Use Video** for long, non-interactive backgrounds.
|
|
97
|
+
|
|
98
|
+
<br />
|
|
69
99
|
|
|
70
100
|
---
|
|
71
101
|
|
|
72
102
|
## 📦 Installation
|
|
73
103
|
|
|
74
104
|
```bash
|
|
75
|
-
npm install react-scroll-media
|
|
105
|
+
npm install react-scroll-media
|
|
76
106
|
```
|
|
77
107
|
|
|
108
|
+
**or**
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
yarn add react-scroll-media
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
<br />
|
|
115
|
+
|
|
78
116
|
---
|
|
79
117
|
|
|
80
118
|
## 🚀 Usage
|
|
81
119
|
|
|
82
|
-
### Basic Example
|
|
120
|
+
### 🎯 Basic Example
|
|
83
121
|
|
|
84
122
|
The simplest way to use it is with the `ScrollSequence` component.
|
|
85
123
|
|
|
86
124
|
```tsx
|
|
87
|
-
import { ScrollSequence } from 'react-scroll-media';
|
|
125
|
+
import { ScrollSequence } from 'react-scroll-media';
|
|
126
|
+
|
|
127
|
+
const frames = [
|
|
128
|
+
'/images/frame_01.jpg',
|
|
129
|
+
'/images/frame_02.jpg',
|
|
130
|
+
// ...
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
export default function MyPage() {
|
|
134
|
+
return (
|
|
135
|
+
<div style={{ height: '200vh' }}>
|
|
136
|
+
<h1>Scroll Down</h1>
|
|
137
|
+
|
|
138
|
+
<ScrollSequence
|
|
139
|
+
source={{ type: 'manual', frames }}
|
|
140
|
+
scrollLength="300vh" // Determines how long the sequence plays
|
|
141
|
+
/>
|
|
142
|
+
|
|
143
|
+
<h1>Continue Scrolling</h1>
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
88
147
|
```
|
|
89
148
|
|
|
149
|
+
<br />
|
|
150
|
+
|
|
90
151
|
### ✨ Scrollytelling & Composition
|
|
91
152
|
|
|
92
153
|
You can nest components inside `ScrollSequence`. They will be placed in the sticky container and can react to the timeline.
|
|
93
154
|
|
|
94
|
-
|
|
155
|
+
<br />
|
|
156
|
+
|
|
157
|
+
#### 📝 Animated Text (`ScrollText`)
|
|
95
158
|
|
|
96
159
|
Animate opacity and position based on scroll progress (0 to 1). Supports enter and exit phases.
|
|
97
160
|
|
|
98
161
|
```tsx
|
|
99
|
-
import { ScrollSequence, ScrollText } from 'react-scroll-media'
|
|
162
|
+
import { ScrollSequence, ScrollText } from 'react-scroll-media';
|
|
163
|
+
|
|
164
|
+
<ScrollSequence source={...} scrollLength="400vh">
|
|
165
|
+
|
|
166
|
+
{/* Fade In (0.1-0.2) -> Hold -> Fade Out (0.8-0.9) */}
|
|
167
|
+
<ScrollText
|
|
168
|
+
start={0.1}
|
|
169
|
+
end={0.2}
|
|
170
|
+
exitStart={0.8}
|
|
171
|
+
exitEnd={0.9}
|
|
172
|
+
translateY={50}
|
|
173
|
+
className="my-text-overlay"
|
|
174
|
+
>
|
|
175
|
+
Cinematic Experience
|
|
176
|
+
</ScrollText>
|
|
177
|
+
|
|
178
|
+
</ScrollSequence>
|
|
100
179
|
```
|
|
101
180
|
|
|
102
|
-
|
|
181
|
+
<br />
|
|
182
|
+
|
|
183
|
+
#### 💬 Word Reveal (`ScrollWordReveal`)
|
|
103
184
|
|
|
104
185
|
Reveals text word-by-word as you scroll.
|
|
105
186
|
|
|
106
187
|
```tsx
|
|
107
|
-
import { ScrollWordReveal } from 'react-scroll-media'
|
|
188
|
+
import { ScrollWordReveal } from 'react-scroll-media';
|
|
189
|
+
|
|
190
|
+
<ScrollWordReveal
|
|
191
|
+
text="Experience the smooth cinematic scroll."
|
|
192
|
+
start={0.4}
|
|
193
|
+
end={0.6}
|
|
194
|
+
style={{ fontSize: '2rem', color: 'white' }}
|
|
195
|
+
/>
|
|
108
196
|
```
|
|
109
197
|
|
|
110
|
-
|
|
198
|
+
<br />
|
|
199
|
+
|
|
200
|
+
### 🔧 Advanced: Custom Hooks
|
|
111
201
|
|
|
112
202
|
For full control over the specialized UI, use the headless hooks.
|
|
113
203
|
|
|
204
|
+
<br />
|
|
205
|
+
|
|
114
206
|
#### `useScrollSequence`
|
|
115
207
|
|
|
116
208
|
Manages the canvas image controller.
|
|
117
209
|
|
|
118
210
|
```tsx
|
|
119
|
-
import { useScrollSequence } from 'react-scroll-media';
|
|
211
|
+
import { useScrollSequence } from 'react-scroll-media';
|
|
212
|
+
|
|
213
|
+
const CustomScroller = () => {
|
|
214
|
+
// ... setup refs
|
|
215
|
+
const { containerRef, canvasRef, isLoaded } = useScrollSequence({ ... });
|
|
216
|
+
// ... render custom structure
|
|
217
|
+
};
|
|
120
218
|
```
|
|
121
219
|
|
|
220
|
+
<br />
|
|
221
|
+
|
|
122
222
|
#### `useScrollTimeline`
|
|
123
223
|
|
|
124
224
|
Subscribe to the scroll timeline in any component.
|
|
125
225
|
|
|
126
226
|
```tsx
|
|
127
|
-
import { useScrollTimeline } from 'react-scroll-media';
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
---
|
|
131
|
-
|
|
132
|
-
## ⚙️ Configuration
|
|
133
|
-
|
|
134
|
-
### `ScrollSequence` Props
|
|
135
|
-
|
|
136
|
-
Prop
|
|
137
|
-
|
|
138
|
-
Type
|
|
139
|
-
|
|
140
|
-
Default
|
|
141
|
-
|
|
142
|
-
Description
|
|
143
|
-
|
|
144
|
-
`source`
|
|
145
|
-
|
|
146
|
-
`SequenceSource`
|
|
147
|
-
|
|
148
|
-
**Required**
|
|
149
|
-
|
|
150
|
-
Defines where images come from.
|
|
151
|
-
|
|
152
|
-
`scrollLength`
|
|
153
|
-
|
|
154
|
-
`string`
|
|
155
|
-
|
|
156
|
-
`"300vh"`
|
|
157
|
-
|
|
158
|
-
Height of the container (animation duration).
|
|
159
|
-
|
|
160
|
-
`memoryStrategy`
|
|
161
|
-
|
|
162
|
-
`"eager" | "lazy"`
|
|
163
|
-
|
|
164
|
-
`"eager"`
|
|
165
|
-
|
|
166
|
-
Optimization strategy.
|
|
167
|
-
|
|
168
|
-
`lazyBuffer`
|
|
169
|
-
|
|
170
|
-
`number`
|
|
171
|
-
|
|
172
|
-
`10`
|
|
173
|
-
|
|
174
|
-
Number of frames to keep loaded in lazy mode.
|
|
175
|
-
|
|
176
|
-
`fallback`
|
|
177
|
-
|
|
178
|
-
`ReactNode`
|
|
179
|
-
|
|
180
|
-
`null`
|
|
181
|
-
|
|
182
|
-
Loading state component.
|
|
183
|
-
|
|
184
|
-
`accessibilityLabel`
|
|
185
|
-
|
|
186
|
-
`string`
|
|
187
|
-
|
|
188
|
-
`"Scroll sequence"`
|
|
189
|
-
|
|
190
|
-
ARIA label for the canvas. Example: `"360 degree view of the product"`.
|
|
191
|
-
|
|
192
|
-
`debug`
|
|
193
|
-
|
|
194
|
-
`boolean`
|
|
195
|
-
|
|
196
|
-
`false`
|
|
197
|
-
|
|
198
|
-
Shows debug overlay.
|
|
199
|
-
|
|
200
|
-
`onError`
|
|
201
|
-
|
|
202
|
-
`(error: Error) => void`
|
|
203
|
-
|
|
204
|
-
`undefined`
|
|
205
|
-
|
|
206
|
-
Callback fired when an image fails to load or initialization errors occur.
|
|
207
|
-
|
|
208
|
-
## 📊 Performance & compatibility
|
|
209
|
-
|
|
210
|
-
### Bundle Size
|
|
211
|
-
|
|
212
|
-
- **Minified**: ~22.0 kB
|
|
213
|
-
- **Gzipped**: ~6.08 kB
|
|
214
|
-
- Zero dependencies (uses native Canvas API, no heavyweight libraries).
|
|
215
|
-
|
|
216
|
-
### Browser Support
|
|
227
|
+
import { useScrollTimeline } from 'react-scroll-media';
|
|
217
228
|
|
|
218
|
-
|
|
229
|
+
const MyComponent = () => {
|
|
230
|
+
const { subscribe } = useScrollTimeline();
|
|
219
231
|
|
|
220
|
-
|
|
232
|
+
// Subscribe to progress (0-1)
|
|
233
|
+
useEffect(() => subscribe((progress) => {
|
|
234
|
+
console.log('Progress:', progress);
|
|
235
|
+
}), [subscribe]);
|
|
221
236
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
✅
|
|
227
|
-
|
|
228
|
-
Full support (OffscreenCanvas enabled)
|
|
229
|
-
|
|
230
|
-
Firefox
|
|
231
|
-
|
|
232
|
-
✅
|
|
233
|
-
|
|
234
|
-
Full support
|
|
235
|
-
|
|
236
|
-
Safari
|
|
237
|
-
|
|
238
|
-
✅
|
|
239
|
-
|
|
240
|
-
Full support (Desktop & Mobile)
|
|
241
|
-
|
|
242
|
-
Edge
|
|
243
|
-
|
|
244
|
-
✅
|
|
245
|
-
|
|
246
|
-
Full support
|
|
247
|
-
|
|
248
|
-
IE11
|
|
249
|
-
|
|
250
|
-
❌
|
|
251
|
-
|
|
252
|
-
Not supported (Missing ES6/Canvas features)
|
|
253
|
-
|
|
254
|
-
### Accessibility (A11y)
|
|
255
|
-
|
|
256
|
-
- **Keyboard Navigation**: Users can scrub through the sequence using standard keyboard controls (Arrow Keys, Spacebar, Page Up/Down) because it relies on native scrolling.
|
|
257
|
-
- **Screen Readers**: Add `accessibilityLabel` to `ScrollSequence` to provide a description for the canvas. Canvas has `role="img"`.
|
|
258
|
-
- **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.
|
|
259
|
-
|
|
260
|
-
### Memory Usage (Benchmarks)
|
|
261
|
-
|
|
262
|
-
Tested on 1080p frames.
|
|
263
|
-
|
|
264
|
-
Frames
|
|
265
|
-
|
|
266
|
-
Strategy
|
|
267
|
-
|
|
268
|
-
Memory
|
|
269
|
-
|
|
270
|
-
Recommendation
|
|
271
|
-
|
|
272
|
-
100
|
|
273
|
-
|
|
274
|
-
`eager`
|
|
275
|
-
|
|
276
|
-
~50MB
|
|
277
|
-
|
|
278
|
-
Instant seeking, smooth.
|
|
279
|
-
|
|
280
|
-
500
|
|
281
|
-
|
|
282
|
-
`eager`
|
|
237
|
+
return <div>...</div>;
|
|
238
|
+
};
|
|
239
|
+
```
|
|
283
240
|
|
|
284
|
-
|
|
241
|
+
<br />
|
|
285
242
|
|
|
286
|
-
|
|
243
|
+
---
|
|
287
244
|
|
|
288
|
-
|
|
245
|
+
## ⚙️ Configuration
|
|
289
246
|
|
|
290
|
-
`
|
|
247
|
+
### `ScrollSequence` Props
|
|
291
248
|
|
|
292
|
-
|
|
249
|
+
<table>
|
|
250
|
+
<thead>
|
|
251
|
+
<tr>
|
|
252
|
+
<th>Prop</th>
|
|
253
|
+
<th>Type</th>
|
|
254
|
+
<th>Default</th>
|
|
255
|
+
<th>Description</th>
|
|
256
|
+
</tr>
|
|
257
|
+
</thead>
|
|
258
|
+
<tbody>
|
|
259
|
+
<tr>
|
|
260
|
+
<td><code>source</code></td>
|
|
261
|
+
<td><code>SequenceSource</code></td>
|
|
262
|
+
<td><strong>Required</strong></td>
|
|
263
|
+
<td>Defines where images come from.</td>
|
|
264
|
+
</tr>
|
|
265
|
+
<tr>
|
|
266
|
+
<td><code>scrollLength</code></td>
|
|
267
|
+
<td><code>string</code></td>
|
|
268
|
+
<td><code>"300vh"</code></td>
|
|
269
|
+
<td>Height of the container (animation duration).</td>
|
|
270
|
+
</tr>
|
|
271
|
+
<tr>
|
|
272
|
+
<td><code>memoryStrategy</code></td>
|
|
273
|
+
<td><code>"eager" | "lazy"</code></td>
|
|
274
|
+
<td><code>"eager"</code></td>
|
|
275
|
+
<td>Optimization strategy.</td>
|
|
276
|
+
</tr>
|
|
277
|
+
<tr>
|
|
278
|
+
<td><code>lazyBuffer</code></td>
|
|
279
|
+
<td><code>number</code></td>
|
|
280
|
+
<td><code>10</code></td>
|
|
281
|
+
<td>Number of frames to keep loaded in lazy mode.</td>
|
|
282
|
+
</tr>
|
|
283
|
+
<tr>
|
|
284
|
+
<td><code>fallback</code></td>
|
|
285
|
+
<td><code>ReactNode</code></td>
|
|
286
|
+
<td><code>null</code></td>
|
|
287
|
+
<td>Loading state component.</td>
|
|
288
|
+
</tr>
|
|
289
|
+
<tr>
|
|
290
|
+
<td><code>accessibilityLabel</code></td>
|
|
291
|
+
<td><code>string</code></td>
|
|
292
|
+
<td><code>"Scroll sequence"</code></td>
|
|
293
|
+
<td>ARIA label for the canvas. Example: <code>"360 degree view of the product"</code>.</td>
|
|
294
|
+
</tr>
|
|
295
|
+
<tr>
|
|
296
|
+
<td><code>debug</code></td>
|
|
297
|
+
<td><code>boolean</code></td>
|
|
298
|
+
<td><code>false</code></td>
|
|
299
|
+
<td>Shows debug overlay.</td>
|
|
300
|
+
</tr>
|
|
301
|
+
<tr>
|
|
302
|
+
<td><code>onError</code></td>
|
|
303
|
+
<td><code>(error: Error) => void</code></td>
|
|
304
|
+
<td><code>undefined</code></td>
|
|
305
|
+
<td>Callback fired when an image fails to load or initialization errors occur.</td>
|
|
306
|
+
</tr>
|
|
307
|
+
</tbody>
|
|
308
|
+
</table>
|
|
309
|
+
|
|
310
|
+
<br />
|
|
293
311
|
|
|
294
|
-
|
|
312
|
+
---
|
|
295
313
|
|
|
296
|
-
|
|
314
|
+
## 📊 Performance & Compatibility
|
|
315
|
+
|
|
316
|
+
### 📦 Bundle Size
|
|
317
|
+
|
|
318
|
+
| Metric | Size |
|
|
319
|
+
|--------|------|
|
|
320
|
+
| **Minified** | ~22.0 kB |
|
|
321
|
+
| **Gzipped** | ~6.08 kB |
|
|
322
|
+
|
|
323
|
+
✨ **Zero dependencies** — Uses native Canvas API, no heavyweight libraries.
|
|
324
|
+
|
|
325
|
+
<br />
|
|
326
|
+
|
|
327
|
+
### 🌐 Browser Support
|
|
328
|
+
|
|
329
|
+
<table>
|
|
330
|
+
<thead>
|
|
331
|
+
<tr>
|
|
332
|
+
<th>Browser</th>
|
|
333
|
+
<th>Status</th>
|
|
334
|
+
<th>Note</th>
|
|
335
|
+
</tr>
|
|
336
|
+
</thead>
|
|
337
|
+
<tbody>
|
|
338
|
+
<tr>
|
|
339
|
+
<td>Chrome</td>
|
|
340
|
+
<td>✅</td>
|
|
341
|
+
<td>Full support (OffscreenCanvas enabled)</td>
|
|
342
|
+
</tr>
|
|
343
|
+
<tr>
|
|
344
|
+
<td>Firefox</td>
|
|
345
|
+
<td>✅</td>
|
|
346
|
+
<td>Full support</td>
|
|
347
|
+
</tr>
|
|
348
|
+
<tr>
|
|
349
|
+
<td>Safari</td>
|
|
350
|
+
<td>✅</td>
|
|
351
|
+
<td>Full support (Desktop & Mobile)</td>
|
|
352
|
+
</tr>
|
|
353
|
+
<tr>
|
|
354
|
+
<td>Edge</td>
|
|
355
|
+
<td>✅</td>
|
|
356
|
+
<td>Full support</td>
|
|
357
|
+
</tr>
|
|
358
|
+
<tr>
|
|
359
|
+
<td>IE11</td>
|
|
360
|
+
<td>❌</td>
|
|
361
|
+
<td>Not supported (Missing ES6/Canvas features)</td>
|
|
362
|
+
</tr>
|
|
363
|
+
</tbody>
|
|
364
|
+
</table>
|
|
365
|
+
|
|
366
|
+
<br />
|
|
367
|
+
|
|
368
|
+
### ♿ Accessibility (A11y)
|
|
369
|
+
|
|
370
|
+
- **🎹 Keyboard Navigation** — Users can scrub through the sequence using standard keyboard controls (Arrow Keys, Spacebar, Page Up/Down) because it relies on native scrolling.
|
|
371
|
+
|
|
372
|
+
- **🔊 Screen Readers** — Add `accessibilityLabel` to `ScrollSequence` to provide a description for the canvas. Canvas has `role="img"`.
|
|
373
|
+
|
|
374
|
+
- **🎭 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.
|
|
375
|
+
|
|
376
|
+
<br />
|
|
377
|
+
|
|
378
|
+
### 💾 Memory Usage (Benchmarks)
|
|
379
|
+
|
|
380
|
+
*Tested on 1080p frames.*
|
|
381
|
+
|
|
382
|
+
<table>
|
|
383
|
+
<thead>
|
|
384
|
+
<tr>
|
|
385
|
+
<th>Frames</th>
|
|
386
|
+
<th>Strategy</th>
|
|
387
|
+
<th>Memory</th>
|
|
388
|
+
<th>Recommendation</th>
|
|
389
|
+
</tr>
|
|
390
|
+
</thead>
|
|
391
|
+
<tbody>
|
|
392
|
+
<tr>
|
|
393
|
+
<td>100</td>
|
|
394
|
+
<td><code>eager</code></td>
|
|
395
|
+
<td>30MB</td>
|
|
396
|
+
<td>Instant seeking, smooth.</td>
|
|
397
|
+
</tr>
|
|
398
|
+
<tr>
|
|
399
|
+
<td>500</td>
|
|
400
|
+
<td><code>eager</code></td>
|
|
401
|
+
<td>46MB</td>
|
|
402
|
+
<td>High RAM usage.</td>
|
|
403
|
+
</tr>
|
|
404
|
+
<tr>
|
|
405
|
+
<td>1000</td>
|
|
406
|
+
<td><code>eager</code></td>
|
|
407
|
+
<td>57MB</td>
|
|
408
|
+
<td>Very high RAM usage.</td>
|
|
409
|
+
</tr>
|
|
410
|
+
<tr>
|
|
411
|
+
<td>100</td>
|
|
412
|
+
<td><code>lazy</code></td>
|
|
413
|
+
<td>25MB</td>
|
|
414
|
+
<td>Low memory usage.</td>
|
|
415
|
+
</tr>
|
|
416
|
+
<tr>
|
|
417
|
+
<td>500</td>
|
|
418
|
+
<td><code>lazy</code></td>
|
|
419
|
+
<td>30MB</td>
|
|
420
|
+
<td>Low memory usage.</td>
|
|
421
|
+
</tr>
|
|
422
|
+
<tr>
|
|
423
|
+
<td>1000</td>
|
|
424
|
+
<td><code>lazy</code></td>
|
|
425
|
+
<td>45MB</td>
|
|
426
|
+
<td><strong>⭐ Recommended</strong>. Kept flat constant.</td>
|
|
427
|
+
</tr>
|
|
428
|
+
</tbody>
|
|
429
|
+
</table>
|
|
430
|
+
|
|
431
|
+
<br />
|
|
432
|
+
|
|
433
|
+
### 🛡️ Error Handling & Fallbacks
|
|
297
434
|
|
|
298
435
|
Network errors are handled gracefully. You can provide a fallback UI that displays while images are loading or if they fail.
|
|
299
436
|
|
|
300
437
|
```tsx
|
|
301
|
-
<ScrollSequence
|
|
438
|
+
<ScrollSequence
|
|
439
|
+
source={{ type: 'manifest', url: '/bad_url.json' }}
|
|
440
|
+
fallback={<div className="error">Failed to load sequence</div>}
|
|
441
|
+
onError={(e) => console.error("Sequence error:", e)}
|
|
442
|
+
/>
|
|
302
443
|
```
|
|
303
444
|
|
|
304
|
-
|
|
445
|
+
<br />
|
|
446
|
+
|
|
447
|
+
### 🚨 Error Boundaries
|
|
305
448
|
|
|
306
449
|
For robust production apps, wrap `ScrollSequence` in an Error Boundary to catch unexpected crashes:
|
|
307
450
|
|
|
308
451
|
```tsx
|
|
309
|
-
|
|
310
|
-
|
|
452
|
+
class ErrorBoundary extends React.Component<
|
|
453
|
+
{ fallback: React.ReactNode, children: React.ReactNode },
|
|
454
|
+
{ hasError: boolean }
|
|
455
|
+
> {
|
|
311
456
|
state = { hasError: false };
|
|
312
|
-
|
|
457
|
+
|
|
458
|
+
static getDerivedStateFromError() {
|
|
459
|
+
return { hasError: true };
|
|
460
|
+
}
|
|
461
|
+
|
|
313
462
|
render() {
|
|
314
463
|
if (this.state.hasError) return this.props.fallback;
|
|
315
464
|
return this.props.children;
|
|
@@ -321,65 +470,101 @@ class ErrorBoundary extends React.Component<{ fallback: React.ReactNode, childre
|
|
|
321
470
|
<ScrollSequence source={...} />
|
|
322
471
|
</ErrorBoundary>
|
|
323
472
|
```
|
|
324
|
-
```
|
|
325
473
|
|
|
326
|
-
|
|
474
|
+
<br />
|
|
475
|
+
|
|
476
|
+
### 🔄 Multi-Instance & Nested Scroll
|
|
327
477
|
|
|
328
478
|
`react-scroll-media` automatically handles multiple instances on the same page. Each instance:
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
479
|
+
|
|
480
|
+
1. Registers with a shared `RAF` loop (singleton) for optimal performance.
|
|
481
|
+
2. Calculates its own progress independently.
|
|
482
|
+
3. Should have a unique `scrollLength` or container setup.
|
|
483
|
+
|
|
484
|
+
<br />
|
|
332
485
|
|
|
333
486
|
---
|
|
334
487
|
|
|
335
488
|
## 🏗️ Architecture
|
|
336
489
|
|
|
337
|
-
### `SequenceSource` Options
|
|
490
|
+
### 📂 `SequenceSource` Options
|
|
338
491
|
|
|
339
|
-
|
|
492
|
+
<br />
|
|
493
|
+
|
|
494
|
+
#### **1. Manual Mode** (Pass array directly)
|
|
340
495
|
|
|
341
496
|
```ts
|
|
342
|
-
{
|
|
497
|
+
{
|
|
498
|
+
type: 'manual',
|
|
499
|
+
frames: ['/img/1.jpg', '/img/2.jpg']
|
|
500
|
+
}
|
|
343
501
|
```
|
|
344
502
|
|
|
345
|
-
|
|
503
|
+
<br />
|
|
504
|
+
|
|
505
|
+
#### **2. Pattern Mode** (Generate URLs)
|
|
346
506
|
|
|
347
507
|
```ts
|
|
348
|
-
{
|
|
508
|
+
{
|
|
509
|
+
type: 'pattern',
|
|
510
|
+
url: '/assets/sequence_{index}.jpg', // {index} is replaced
|
|
511
|
+
start: 1, // Start index
|
|
512
|
+
end: 100, // End index
|
|
513
|
+
pad: 4 // Zero padding (e.g. 1 -> 0001)
|
|
514
|
+
}
|
|
349
515
|
```
|
|
350
516
|
|
|
351
|
-
|
|
517
|
+
<br />
|
|
518
|
+
|
|
519
|
+
#### **3. Manifest Mode** (Fetch JSON)
|
|
352
520
|
|
|
353
521
|
```ts
|
|
354
|
-
{
|
|
522
|
+
{
|
|
523
|
+
type: 'manifest',
|
|
524
|
+
url: '/sequence.json'
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// JSON format: { "frames": ["url1", "url2"] } OR pattern config
|
|
355
528
|
```
|
|
356
529
|
|
|
357
|
-
>
|
|
530
|
+
> **💡 Note**: Manifests are cached in memory by URL. To force a refresh, append a query param (e.g. `?v=2`).
|
|
358
531
|
|
|
359
|
-
|
|
532
|
+
<br />
|
|
360
533
|
|
|
361
|
-
|
|
534
|
+
---
|
|
362
535
|
|
|
363
|
-
|
|
536
|
+
## 🎨 How it Works (The "Sticky" Technique)
|
|
364
537
|
|
|
365
538
|
Unlike libraries that use `position: fixed` or JS-based scroll locking (which breaks refreshing and feels unnatural), we use **CSS Sticky Positioning**.
|
|
366
539
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
This gives a precise 0.0 to 1.0 value tied to the pixel position of the scrollbar.
|
|
540
|
+
<br />
|
|
541
|
+
|
|
542
|
+
### 🔧 Technical Breakdown
|
|
543
|
+
|
|
544
|
+
1. **Container (`relative`)** — This element has the height you specify (e.g., `300vh`). It occupies space in the document flow.
|
|
545
|
+
|
|
546
|
+
2. **Sticky Wrapper (`sticky`)** — Inside the container, we place a `div` that is `100vh` tall and `sticky` at `top: 0`.
|
|
377
547
|
|
|
378
|
-
|
|
548
|
+
3. **Canvas** — The `<canvas>` sits inside the sticky wrapper.
|
|
379
549
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
550
|
+
4. **Math** — As you scroll the container, the sticky wrapper stays pinned to the viewport. We calculate:
|
|
551
|
+
|
|
552
|
+
```ts
|
|
553
|
+
progress = -containerRect.top / (containerHeight - viewportHeight)
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
This gives a precise **0.0 to 1.0** value tied to the pixel position of the scrollbar.
|
|
557
|
+
|
|
558
|
+
<br />
|
|
559
|
+
|
|
560
|
+
### 💡 Memory Strategy
|
|
561
|
+
|
|
562
|
+
- **"eager" (Default)** — Best for sequences < 200 frames. Preloads all images into `HTMLImageElement` instances. Instant seeking, smooth playback. High memory usage.
|
|
563
|
+
|
|
564
|
+
- **"lazy"** — Best for long sequences (500+ frames). Only keeps the current frame and its neighbors in memory. Saves RAM, prevents crashes.
|
|
565
|
+
- Buffer size defaults to ±10 frames but can be customized via `lazyBuffer`.
|
|
566
|
+
|
|
567
|
+
<br />
|
|
383
568
|
|
|
384
569
|
---
|
|
385
570
|
|
|
@@ -388,17 +573,37 @@ Unlike libraries that use `position: fixed` or JS-based scroll locking (which br
|
|
|
388
573
|
Enable the debug overlay to inspect your sequence in production:
|
|
389
574
|
|
|
390
575
|
```tsx
|
|
391
|
-
<ScrollSequence
|
|
576
|
+
<ScrollSequence
|
|
577
|
+
source={...}
|
|
578
|
+
debug={true}
|
|
579
|
+
/>
|
|
392
580
|
```
|
|
393
581
|
|
|
582
|
+
<br />
|
|
583
|
+
|
|
394
584
|
**Output:**
|
|
395
585
|
|
|
396
586
|
```
|
|
397
|
-
Progress: 0.
|
|
587
|
+
Progress: 0.45
|
|
588
|
+
Frame: 45 / 100
|
|
398
589
|
```
|
|
399
590
|
|
|
400
|
-
This overlay is updated directly via DOM manipulation (bypassing React renders) for zero overhead
|
|
591
|
+
This overlay is updated directly via DOM manipulation (bypassing React renders) for **zero overhead**.
|
|
592
|
+
|
|
593
|
+
<br />
|
|
401
594
|
|
|
402
595
|
---
|
|
403
596
|
|
|
404
|
-
|
|
597
|
+
<div align="center">
|
|
598
|
+
|
|
599
|
+
## 📄 License
|
|
600
|
+
|
|
601
|
+
**MIT** © 2026 **Thanniru Sai Teja**
|
|
602
|
+
|
|
603
|
+
<br />
|
|
604
|
+
|
|
605
|
+
Made with ❤️ for the React community
|
|
606
|
+
|
|
607
|
+
[⬆ Back to Top](#-react-scroll-media)
|
|
608
|
+
|
|
609
|
+
</div>
|