Lightweight scrollytelling library using IntersectionObserver for scroll-driven interactive narratives
npx @tessl/cli install tessl/npm-scrollama@3.2.00
# Scrollama
1
2
Scrollama is a lightweight JavaScript library for creating scrollytelling (scroll-driven storytelling) experiences using the IntersectionObserver API. It provides a simple interface for detecting when elements enter or exit the viewport during scrolling, with support for step-based interactions, progress tracking, custom offsets, and sticky graphic implementations.
3
4
## Package Information
5
6
- **Package Name**: scrollama
7
- **Package Type**: npm
8
- **Language**: JavaScript (with TypeScript definitions)
9
- **Installation**: `npm install scrollama`
10
11
## Core Imports
12
13
```javascript
14
import scrollama from "scrollama";
15
```
16
17
For CommonJS:
18
19
```javascript
20
const scrollama = require("scrollama");
21
```
22
23
Browser (CDN):
24
25
```html
26
<script src="https://unpkg.com/scrollama"></script>
27
```
28
29
## Basic Usage
30
31
```javascript
32
import scrollama from "scrollama";
33
34
// Create scrollama instance
35
const scroller = scrollama();
36
37
// Setup with step elements and callbacks
38
scroller
39
.setup({
40
step: ".step", // required: CSS selector for step elements
41
offset: 0.5, // trigger when element is 50% in viewport
42
debug: false // optional visual debugging
43
})
44
.onStepEnter((response) => {
45
// Fired when step enters offset threshold
46
const { element, index, direction } = response;
47
console.log("Step entered:", index, direction);
48
})
49
.onStepExit((response) => {
50
// Fired when step exits offset threshold
51
const { element, index, direction } = response;
52
console.log("Step exited:", index, direction);
53
});
54
```
55
56
## Architecture
57
58
Scrollama uses the IntersectionObserver API for performance optimization over traditional scroll events. Key components:
59
60
- **Factory Function**: `scrollama()` creates instances with chainable method calls
61
- **Observer Management**: Automatically manages IntersectionObserver instances per step
62
- **Resize Handling**: Built-in ResizeObserver automatically handles viewport changes
63
- **Progress Tracking**: Optional fine-grained progress monitoring within steps
64
- **Debug Mode**: Visual overlays for development and testing
65
66
## Capabilities
67
68
### Instance Creation
69
70
Creates a new scrollama instance for managing scroll-driven interactions.
71
72
```javascript { .api }
73
/**
74
* Creates a new scrollama instance
75
* @returns ScrollamaInstance - Instance with chainable configuration methods
76
*/
77
function scrollama(): ScrollamaInstance;
78
```
79
80
### Setup Configuration
81
82
Configures the scrollama instance with step elements and interaction settings.
83
84
```javascript { .api }
85
/**
86
* Configure scrollama instance with steps and options
87
* @param options - Configuration object
88
* @returns ScrollamaInstance - For method chaining
89
*/
90
setup(options: ScrollamaOptions): ScrollamaInstance;
91
92
interface ScrollamaOptions {
93
/** Required: CSS selector, NodeList, HTMLElement[], or single HTMLElement for step elements */
94
step: string | NodeList | HTMLElement[] | HTMLElement;
95
/** Trigger position in viewport: 0-1 or string with "px" (default: 0.5) */
96
offset?: number | string;
97
/** Enable incremental progress updates (default: false) */
98
progress?: boolean;
99
/** Granularity of progress updates in pixels, minimum 1 (default: 4) */
100
threshold?: number;
101
/** Only trigger steps once, then remove listeners (default: false) */
102
once?: boolean;
103
/** Show visual debugging overlays (default: false) */
104
debug?: boolean;
105
/** Parent element for step selector, useful for shadow DOM */
106
parent?: HTMLElement;
107
/** Parent element for scroll story, for overflow: scroll/auto containers */
108
container?: HTMLElement;
109
/** Element used as viewport for visibility checking (default: browser viewport) */
110
root?: HTMLElement;
111
}
112
```
113
114
**Usage Example:**
115
116
```javascript
117
// Basic setup
118
scroller.setup({
119
step: ".story-step"
120
});
121
122
// Advanced setup with progress tracking
123
scroller.setup({
124
step: ".story-step",
125
offset: 0.8, // Trigger at 80% viewport height
126
progress: true, // Enable progress callbacks
127
threshold: 2, // Higher granularity
128
debug: true, // Show debug overlays
129
container: document.querySelector('.scroll-container')
130
});
131
```
132
133
### Step Event Callbacks
134
135
Register callbacks for step enter and exit events.
136
137
```javascript { .api }
138
/**
139
* Callback fired when step elements enter the offset threshold
140
* @param callback - Function receiving step event data
141
* @returns ScrollamaInstance - For method chaining
142
*/
143
onStepEnter(callback: (response: CallbackResponse) => void): ScrollamaInstance;
144
145
/**
146
* Callback fired when step elements exit the offset threshold
147
* @param callback - Function receiving step event data
148
* @returns ScrollamaInstance - For method chaining
149
*/
150
onStepExit(callback: (response: CallbackResponse) => void): ScrollamaInstance;
151
152
interface CallbackResponse {
153
/** The DOM element that triggered the event */
154
element: HTMLElement;
155
/** Zero-based index of the step element */
156
index: number;
157
/** Scroll direction when event fired */
158
direction: "up" | "down";
159
}
160
```
161
162
**Usage Example:**
163
164
```javascript
165
scroller
166
.onStepEnter((response) => {
167
const { element, index, direction } = response;
168
element.classList.add('is-active');
169
170
// Handle different steps
171
if (index === 0) {
172
// First step logic
173
} else if (index === 2) {
174
// Third step logic
175
}
176
})
177
.onStepExit((response) => {
178
const { element, index, direction } = response;
179
element.classList.remove('is-active');
180
});
181
```
182
183
### Progress Tracking
184
185
Monitor fine-grained progress within step elements (requires `progress: true` in setup).
186
187
```javascript { .api }
188
/**
189
* Callback fired for incremental progress through steps
190
* @param callback - Function receiving progress event data
191
* @returns ScrollamaInstance - For method chaining
192
*/
193
onStepProgress(callback: (response: ProgressCallbackResponse) => void): ScrollamaInstance;
194
195
interface ProgressCallbackResponse {
196
/** The DOM element being tracked */
197
element: HTMLElement;
198
/** Zero-based index of the step element */
199
index: number;
200
/** Progress through the step (0.0 to 1.0) */
201
progress: number;
202
/** Current scroll direction */
203
direction: "up" | "down";
204
}
205
```
206
207
**Usage Example:**
208
209
```javascript
210
scroller
211
.setup({
212
step: ".step",
213
progress: true,
214
threshold: 1 // Very granular updates
215
})
216
.onStepProgress((response) => {
217
const { element, index, progress, direction } = response;
218
219
// Update progress bar
220
const progressBar = element.querySelector('.progress-bar');
221
progressBar.style.width = `${progress * 100}%`;
222
223
// Fade in/out based on progress
224
element.style.opacity = progress;
225
});
226
```
227
228
### Offset Management
229
230
Get or set the trigger offset position dynamically.
231
232
```javascript { .api }
233
/**
234
* Get or set the offset trigger value (note: method name is 'offset' in implementation)
235
* @param value - Optional new offset value (0-1 or string with "px")
236
* @returns ScrollamaInstance if setting, current offset value if getting
237
*/
238
offset(value?: number | string): ScrollamaInstance | number;
239
```
240
241
**Usage Example:**
242
243
```javascript
244
// Get current offset
245
const currentOffset = scroller.offset();
246
247
// Set new offset
248
scroller.offset(0.25); // Trigger at 25% viewport height
249
scroller.offset("100px"); // Trigger at 100px from top
250
```
251
252
### Instance Control
253
254
Control scrollama instance state and lifecycle.
255
256
```javascript { .api }
257
/**
258
* Resume observing for trigger changes (if previously disabled)
259
* @returns ScrollamaInstance - For method chaining
260
*/
261
enable(): ScrollamaInstance;
262
263
/**
264
* Stop observing for trigger changes
265
* @returns ScrollamaInstance - For method chaining
266
*/
267
disable(): ScrollamaInstance;
268
269
/**
270
* Manual resize trigger (automatic with built-in ResizeObserver)
271
* @returns ScrollamaInstance - For method chaining
272
*/
273
resize(): ScrollamaInstance;
274
275
/**
276
* Remove all observers and callback functions
277
* @returns void
278
*/
279
destroy(): void;
280
```
281
282
**Usage Example:**
283
284
```javascript
285
// Temporarily disable
286
scroller.disable();
287
288
// Re-enable later
289
scroller.enable();
290
291
// Clean up when done
292
scroller.destroy();
293
```
294
295
## Custom Offset Data Attributes
296
297
Override the global offset for individual step elements using data attributes.
298
299
```html { .api }
300
<!-- Use percentage (0-1) -->
301
<div class="step" data-offset="0.25">Step with 25% offset</div>
302
303
<!-- Use pixels -->
304
<div class="step" data-offset="100px">Step with 100px offset</div>
305
306
<!-- Use default global offset -->
307
<div class="step">Step with global offset</div>
308
```
309
310
## Types
311
312
Complete TypeScript type definitions for all interfaces and callbacks.
313
314
```typescript { .api }
315
interface ScrollamaInstance {
316
setup(options: ScrollamaOptions): ScrollamaInstance;
317
onStepEnter(callback: StepCallback): ScrollamaInstance;
318
onStepExit(callback: StepCallback): ScrollamaInstance;
319
onStepProgress(callback: StepProgressCallback): ScrollamaInstance;
320
offset(value?: number | string): ScrollamaInstance | number;
321
resize(): ScrollamaInstance;
322
enable(): ScrollamaInstance;
323
disable(): ScrollamaInstance;
324
destroy(): void;
325
}
326
327
type DecimalType = 0 | 0.1 | 0.2 | 0.3 | 0.4 | 0.5 | 0.6 | 0.7 | 0.8 | 0.9 | 1;
328
329
type StepCallback = (response: CallbackResponse) => void;
330
type StepProgressCallback = (response: ProgressCallbackResponse) => void;
331
```
332
333
## Common Patterns
334
335
### Sticky Graphic Side-by-Side
336
337
```html
338
<div class="container">
339
<div class="graphic">
340
<!-- Fixed graphic content -->
341
</div>
342
<div class="scroller">
343
<div class="step" data-step="1">First step content</div>
344
<div class="step" data-step="2">Second step content</div>
345
<div class="step" data-step="3">Third step content</div>
346
</div>
347
</div>
348
```
349
350
```css
351
.container {
352
display: flex;
353
}
354
355
.graphic {
356
position: sticky;
357
top: 0;
358
flex: 1;
359
height: 100vh;
360
}
361
362
.scroller {
363
flex: 1;
364
}
365
366
.step {
367
margin-bottom: 80vh;
368
padding: 1rem;
369
}
370
```
371
372
### Mobile-Friendly Pixel Offsets
373
374
```javascript
375
// Use pixel offsets for consistent mobile behavior
376
const isMobile = window.innerWidth < 768;
377
const offset = isMobile ? "150px" : 0.5;
378
379
scroller.setup({
380
step: ".step",
381
offset: offset
382
});
383
```
384
385
### Dynamic Step Management
386
387
```javascript
388
// Add new steps dynamically
389
function addStep(content) {
390
const newStep = document.createElement('div');
391
newStep.className = 'step';
392
newStep.textContent = content;
393
document.querySelector('.scroller').appendChild(newStep);
394
395
// Reconfigure scrollama
396
scroller.setup({
397
step: ".step" // Automatically picks up new steps
398
});
399
}
400
```