react-scroll-media 1.0.0 → 1.0.2

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