scroll-snap-kit 1.1.0 → 2.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fabian Faraz Farid
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -32,6 +32,10 @@ npm install scroll-snap-kit
32
32
  | `onScrollEnd` | Fire a callback when scrolling stops |
33
33
  | `scrollIntoViewIfNeeded` | Only scroll if element is off-screen |
34
34
  | `easeScroll` + `Easings` | Custom easing curves for scroll animation |
35
+ | `scrollSequence` | Chain multiple scroll animations in order |
36
+ | `parallax` | Attach parallax speed multipliers to elements |
37
+ | `scrollProgress` | Track how far through an element the user has scrolled |
38
+ | `snapToSection` | Auto-snap to the nearest section after scrolling stops |
35
39
 
36
40
  | Hook | Description |
37
41
  |------|-------------|
@@ -51,7 +55,8 @@ import {
51
55
  getScrollPosition, onScroll, isInViewport,
52
56
  lockScroll, unlockScroll,
53
57
  scrollSpy, onScrollEnd, scrollIntoViewIfNeeded,
54
- easeScroll, Easings
58
+ easeScroll, Easings,
59
+ scrollSequence, parallax, scrollProgress, snapToSection
55
60
  } from 'scroll-snap-kit';
56
61
  ```
57
62
 
@@ -92,7 +97,6 @@ Returns the current scroll position and scroll percentage for the page or any sc
92
97
  const { x, y, percentX, percentY } = getScrollPosition();
93
98
  // percentY → how far down the page (0–100)
94
99
 
95
- // Works on containers too
96
100
  const pos = getScrollPosition(document.querySelector('.sidebar'));
97
101
  ```
98
102
 
@@ -100,7 +104,7 @@ const pos = getScrollPosition(document.querySelector('.sidebar'));
100
104
 
101
105
  ### `onScroll(callback, options?)`
102
106
 
103
- Throttled scroll listener. Returns a cleanup function to stop listening.
107
+ Throttled scroll listener. Returns a cleanup function.
104
108
 
105
109
  ```js
106
110
  const stop = onScroll(({ x, y, percentX, percentY }) => {
@@ -119,8 +123,6 @@ stop(); // removes the listener
119
123
 
120
124
  ### `isInViewport(element, options?)`
121
125
 
122
- Check whether an element is currently visible in the viewport.
123
-
124
126
  ```js
125
127
  if (isInViewport(document.querySelector('.card'), { threshold: 0.5 })) {
126
128
  // At least 50% of the card is visible
@@ -135,47 +137,44 @@ if (isInViewport(document.querySelector('.card'), { threshold: 0.5 })) {
135
137
 
136
138
  ### `lockScroll()` / `unlockScroll()`
137
139
 
138
- Lock page scroll (e.g. when a modal is open) and restore the exact position on unlock — no layout jump.
139
-
140
140
  ```js
141
141
  lockScroll(); // body stops scrolling, position saved
142
- unlockScroll(); // position restored precisely
142
+ unlockScroll(); // position restored precisely — no layout jump
143
143
  ```
144
144
 
145
145
  ---
146
146
 
147
147
  ### `scrollSpy(sectionsSelector, linksSelector, options?)`
148
148
 
149
- Watches scroll position and automatically adds an active class to the nav link matching the current section. Returns a cleanup function.
149
+ Watches scroll position and automatically adds an active class to the nav link matching the current section.
150
150
 
151
151
  ```js
152
152
  const stop = scrollSpy(
153
- 'section[id]', // sections to spy on
154
- 'nav a', // nav links to highlight
155
- {
156
- offset: 80, // px from top to trigger (default: 0)
157
- activeClass: 'scroll-spy-active' // class to toggle (default: 'scroll-spy-active')
158
- }
153
+ 'section[id]',
154
+ 'nav a',
155
+ { offset: 80, activeClass: 'scroll-spy-active' }
159
156
  );
160
157
 
161
- stop(); // remove the listener
158
+ stop(); // remove listener
162
159
  ```
163
160
 
164
161
  ```css
165
- /* Style the active link however you like */
166
162
  nav a.scroll-spy-active {
167
163
  color: #00ffaa;
168
164
  border-bottom: 1px solid currentColor;
169
165
  }
170
166
  ```
171
167
 
172
- > Links are matched by comparing their `href` to `#sectionId`. Call `scrollSpy` multiple times to target different link groups simultaneously.
168
+ | Option | Type | Default | Description |
169
+ |--------|------|---------|-------------|
170
+ | `offset` | `number` | `0` | px from top to trigger section change |
171
+ | `activeClass` | `string` | `'scroll-spy-active'` | Class added to the active link |
173
172
 
174
173
  ---
175
174
 
176
175
  ### `onScrollEnd(callback, options?)`
177
176
 
178
- Fires a callback once the user has stopped scrolling for a configurable delay. Great for lazy-loading, analytics, or autosave.
177
+ Fires once the user has stopped scrolling for a configurable delay.
179
178
 
180
179
  ```js
181
180
  const stop = onScrollEnd(() => {
@@ -183,7 +182,7 @@ const stop = onScrollEnd(() => {
183
182
  saveScrollPosition();
184
183
  }, { delay: 200 });
185
184
 
186
- stop(); // cleanup
185
+ stop();
187
186
  ```
188
187
 
189
188
  | Option | Type | Default | Description |
@@ -195,13 +194,10 @@ stop(); // cleanup
195
194
 
196
195
  ### `scrollIntoViewIfNeeded(element, options?)`
197
196
 
198
- Scrolls to an element only if it is partially or fully outside the visible viewport. If it's already visible enough, nothing happens — no unnecessary scroll.
197
+ Scrolls to an element only if it is outside the visible viewport. If it's already visible enough, nothing happens.
199
198
 
200
199
  ```js
201
- // Only scrolls if the element is off-screen
202
200
  scrollIntoViewIfNeeded(document.querySelector('.card'));
203
-
204
- // threshold: how much must be visible before we skip scrolling
205
201
  scrollIntoViewIfNeeded(element, { threshold: 0.5, offset: -80 });
206
202
  ```
207
203
 
@@ -215,14 +211,10 @@ scrollIntoViewIfNeeded(element, { threshold: 0.5, offset: -80 });
215
211
 
216
212
  ### `easeScroll(target, options?)` + `Easings`
217
213
 
218
- Scroll to a position with a fully custom easing curve, bypassing the browser's native smooth scroll. Returns a `Promise` that resolves when the animation completes.
214
+ Scroll to a position with a fully custom easing curve. Returns a `Promise` that resolves when animation completes.
219
215
 
220
216
  ```js
221
- // Use a built-in easing
222
- await easeScroll('#contact', {
223
- duration: 800,
224
- easing: Easings.easeOutElastic
225
- });
217
+ await easeScroll('#contact', { duration: 800, easing: Easings.easeOutElastic });
226
218
 
227
219
  // Chain animations
228
220
  await easeScroll('#hero', { duration: 600, easing: Easings.easeInOutCubic });
@@ -236,37 +228,115 @@ easeScroll(element, { easing: t => t * t * t });
236
228
  |--------|------|---------|-------------|
237
229
  | `duration` | `number` | `600` | Animation duration in ms |
238
230
  | `easing` | `(t: number) => number` | `Easings.easeInOutCubic` | Easing function |
239
- | `offset` | `number` | `0` | Pixel offset applied to target position |
231
+ | `offset` | `number` | `0` | Pixel offset applied to target |
232
+
233
+ **Built-in easings:** `linear`, `easeInQuad`, `easeOutQuad`, `easeInOutQuad`, `easeInCubic`, `easeOutCubic`, `easeInOutCubic`, `easeInQuart`, `easeOutQuart`, `easeOutElastic`, `easeOutBounce`
234
+
235
+ ---
236
+
237
+ ### `scrollSequence(steps)`
240
238
 
241
- **Built-in easings:**
239
+ Run multiple `easeScroll` animations one after another as a choreographed sequence. Supports pauses between steps. Returns `{ promise, cancel }`.
242
240
 
243
241
  ```js
244
- import { Easings } from 'scroll-snap-kit';
245
-
246
- Easings.linear
247
- Easings.easeInQuad
248
- Easings.easeOutQuad
249
- Easings.easeInOutQuad
250
- Easings.easeInCubic
251
- Easings.easeOutCubic
252
- Easings.easeInOutCubic // ← default
253
- Easings.easeInQuart
254
- Easings.easeOutQuart
255
- Easings.easeOutElastic // springy overshoot
256
- Easings.easeOutBounce // bouncy landing
242
+ const { promise, cancel } = scrollSequence([
243
+ { target: '#intro', duration: 600 },
244
+ { target: '#features', duration: 800, pause: 400 }, // pause 400ms before next step
245
+ { target: '#pricing', duration: 600, easing: Easings.easeOutElastic },
246
+ ]);
247
+
248
+ await promise; // resolves when all steps complete
249
+ cancel(); // abort at any point mid-sequence
257
250
  ```
258
251
 
252
+ | Step option | Type | Default | Description |
253
+ |-------------|------|---------|-------------|
254
+ | `target` | `Element \| string \| number` | — | Scroll destination (required) |
255
+ | `duration` | `number` | `600` | Duration of this step in ms |
256
+ | `easing` | `Function` | `easeInOutCubic` | Easing for this step |
257
+ | `offset` | `number` | `0` | Pixel offset |
258
+ | `pause` | `number` | `0` | ms to wait after this step before the next |
259
+
260
+ ---
261
+
262
+ ### `parallax(targets, options?)`
263
+
264
+ Attach a parallax scroll effect to one or more elements. Each element moves relative to its original position at the given `speed` multiplier.
265
+
266
+ ```js
267
+ // speed < 1 = moves slower than scroll (background feel)
268
+ // speed > 1 = moves faster than scroll (foreground feel)
269
+ // speed < 0 = moves in the opposite direction to scroll
270
+
271
+ const destroy = parallax('.hero-bg', { speed: 0.4 });
272
+ const destroy = parallax('.clouds', { speed: -0.2, axis: 'x' });
273
+ const destroy = parallax([el1, el2], { speed: 0.6, axis: 'both' });
274
+
275
+ destroy(); // removes the effect and resets all transforms
276
+ ```
277
+
278
+ | Option | Type | Default | Description |
279
+ |--------|------|---------|-------------|
280
+ | `speed` | `number` | `0.5` | Movement multiplier |
281
+ | `axis` | `'y' \| 'x' \| 'both'` | `'y'` | Axis to apply parallax on |
282
+ | `container` | `Element` | `window` | Scrollable container |
283
+
284
+ ---
285
+
286
+ ### `scrollProgress(element, callback, options?)`
287
+
288
+ Track how far the user has scrolled through a specific element, independent of overall page progress.
289
+
290
+ - `0` = element top just entered the bottom of the viewport
291
+ - `1` = element bottom just exited the top of the viewport
292
+
293
+ ```js
294
+ const stop = scrollProgress('#article', (progress) => {
295
+ progressBar.style.width = `${progress * 100}%`;
296
+ if (progress === 1) console.log('Article fully read!');
297
+ });
298
+
299
+ stop(); // cleanup
300
+ ```
301
+
302
+ | Option | Type | Default | Description |
303
+ |--------|------|---------|-------------|
304
+ | `offset` | `number` | `0` | Pixel adjustment to progress calculation |
305
+
306
+ ---
307
+
308
+ ### `snapToSection(sections, options?)`
309
+
310
+ After the user stops scrolling, automatically snap to the nearest section. Returns a destroy function.
311
+
312
+ ```js
313
+ const destroy = snapToSection('section[id]', {
314
+ delay: 150, // ms to wait after scroll stops (default: 150)
315
+ offset: -70, // account for sticky nav (default: 0)
316
+ duration: 500, // snap animation duration (default: 500)
317
+ easing: Easings.easeInOutCubic // snap animation easing
318
+ });
319
+
320
+ destroy(); // remove snap behaviour
321
+ ```
322
+
323
+ | Option | Type | Default | Description |
324
+ |--------|------|---------|-------------|
325
+ | `delay` | `number` | `150` | ms after scrolling stops before snap fires |
326
+ | `offset` | `number` | `0` | Pixel offset applied to snap target |
327
+ | `duration` | `number` | `500` | Snap animation duration in ms |
328
+ | `easing` | `Function` | `Easings.easeInOutCubic` | Snap animation easing |
329
+
330
+ > Works on both window scroll and scrollable containers. Pass an array of elements instead of a selector for more control.
331
+
259
332
  ---
260
333
 
261
334
  ## React Hooks
262
335
 
263
336
  ```js
264
337
  import {
265
- useScrollPosition,
266
- useInViewport,
267
- useScrollTo,
268
- useScrolledPast,
269
- useScrollDirection
338
+ useScrollPosition, useInViewport, useScrollTo,
339
+ useScrolledPast, useScrollDirection
270
340
  } from 'scroll-snap-kit/hooks';
271
341
  ```
272
342
 
@@ -276,129 +346,71 @@ import {
276
346
 
277
347
  ### `useScrollPosition(options?)`
278
348
 
279
- Returns the current scroll position, updated live on scroll.
280
-
281
349
  ```jsx
282
350
  function ProgressBar() {
283
- const { x, y, percentX, percentY } = useScrollPosition({ throttle: 50 });
351
+ const { percentY } = useScrollPosition({ throttle: 50 });
284
352
  return <div style={{ width: `${percentY}%` }} className="progress-bar" />;
285
353
  }
286
354
  ```
287
355
 
288
- | Option | Type | Default | Description |
289
- |--------|------|---------|-------------|
290
- | `throttle` | `number` | `100` | ms between updates |
291
- | `container` | `Element` | `window` | Scrollable container |
292
-
293
- ---
294
-
295
356
  ### `useInViewport(options?)`
296
357
 
297
- Returns a `[ref, inView]` tuple. Attach `ref` to any element to track its viewport visibility using `IntersectionObserver`.
298
-
299
358
  ```jsx
300
359
  function FadeInCard() {
301
360
  const [ref, inView] = useInViewport({ threshold: 0.2, once: true });
302
-
303
361
  return (
304
- <div
305
- ref={ref}
306
- style={{
307
- opacity: inView ? 1 : 0,
308
- transform: inView ? 'translateY(0)' : 'translateY(20px)',
309
- transition: 'all 0.5s ease'
310
- }}
311
- >
312
- I animate in when visible!
362
+ <div ref={ref} style={{ opacity: inView ? 1 : 0, transition: 'opacity 0.5s' }}>
363
+ Fades in when visible!
313
364
  </div>
314
365
  );
315
366
  }
316
367
  ```
317
368
 
318
- | Option | Type | Default | Description |
319
- |--------|------|---------|-------------|
320
- | `threshold` | `number` | `0` | 0–1 portion of element visible to trigger |
321
- | `once` | `boolean` | `false` | Only trigger on first entry, then stop observing |
322
-
323
- ---
324
-
325
369
  ### `useScrollTo()`
326
370
 
327
- Returns a `[containerRef, scrollToTarget]` tuple. Scope smooth scrolling to a specific scrollable container, or fall back to window scroll.
328
-
329
371
  ```jsx
330
- function Sidebar() {
372
+ function Page() {
331
373
  const [containerRef, scrollToTarget] = useScrollTo();
332
374
  const sectionRef = useRef(null);
333
-
334
375
  return (
335
376
  <div ref={containerRef} style={{ overflowY: 'scroll', height: '400px' }}>
336
- <button onClick={() => scrollToTarget(sectionRef.current, { offset: -16 })}>
337
- Jump to section
338
- </button>
339
- <div style={{ height: 300 }} />
340
- <div ref={sectionRef}>Target section</div>
377
+ <button onClick={() => scrollToTarget(sectionRef.current)}>Jump</button>
378
+ <div ref={sectionRef}>Target</div>
341
379
  </div>
342
380
  );
343
381
  }
344
382
  ```
345
383
 
346
- ---
347
-
348
- ### `useScrolledPast(threshold?, options?)`
349
-
350
- Returns `true` once the user has scrolled past a given pixel value. Useful for showing back-to-top buttons, sticky CTAs, and similar patterns.
384
+ ### `useScrolledPast(threshold?)`
351
385
 
352
386
  ```jsx
353
387
  function BackToTopButton() {
354
388
  const scrolledPast = useScrolledPast(300);
355
- return scrolledPast ? (
356
- <button onClick={() => scrollToTop()}>↑ Back to top</button>
357
- ) : null;
389
+ return scrolledPast ? <button onClick={scrollToTop}>↑ Top</button> : null;
358
390
  }
359
391
  ```
360
392
 
361
- | Param | Type | Default | Description |
362
- |-------|------|---------|-------------|
363
- | `threshold` | `number` | `100` | Y pixel value to check against |
364
- | `options.container` | `Element` | `window` | Scrollable container |
365
-
366
- ---
367
-
368
393
  ### `useScrollDirection()`
369
394
 
370
- Returns the current scroll direction: `'up'`, `'down'`, or `null` on initial render.
371
-
372
395
  ```jsx
373
396
  function HideOnScrollNav() {
374
397
  const direction = useScrollDirection();
375
-
376
398
  return (
377
- <nav style={{
378
- transform: direction === 'down' ? 'translateY(-100%)' : 'translateY(0)',
379
- transition: 'transform 0.3s ease'
380
- }}>
399
+ <nav style={{ transform: direction === 'down' ? 'translateY(-100%)' : 'translateY(0)' }}>
381
400
  My Navbar
382
401
  </nav>
383
402
  );
384
403
  }
385
404
  ```
386
405
 
387
- | Option | Type | Default | Description |
388
- |--------|------|---------|-------------|
389
- | `throttle` | `number` | `100` | ms between direction checks |
390
-
391
406
  ---
392
407
 
393
408
  ## Tree-shaking
394
409
 
395
- All exports are named and side-effect free — you only ship what you import:
410
+ All exports are named and side-effect free:
396
411
 
397
412
  ```js
398
- // Only pulls in ~400 bytes
399
- import { scrollToTop } from 'scroll-snap-kit';
400
-
401
- // Import utils and hooks separately for maximum tree-shaking
413
+ import { scrollToTop } from 'scroll-snap-kit'; // ~400 bytes
402
414
  import { onScroll, scrollSpy } from 'scroll-snap-kit/utils';
403
415
  import { useScrollPosition } from 'scroll-snap-kit/hooks';
404
416
  ```
@@ -407,12 +419,18 @@ import { useScrollPosition } from 'scroll-snap-kit/hooks';
407
419
 
408
420
  ## Browser support
409
421
 
410
- All modern browsers (Chrome, Firefox, Safari, Edge). `easeScroll` uses `requestAnimationFrame`. `useInViewport` and `isInViewport` use `IntersectionObserver` — supported everywhere modern; polyfill if you need IE11.
422
+ All modern browsers. `easeScroll` and `scrollSequence` use `requestAnimationFrame`. `useInViewport` / `isInViewport` use `IntersectionObserver` — polyfill if you need IE11.
411
423
 
412
424
  ---
413
425
 
414
426
  ## Changelog
415
427
 
428
+ ### v1.2.0
429
+ - ✨ `scrollSequence()` — chain multiple scroll animations with pauses and cancel support
430
+ - ✨ `parallax()` — multi-element parallax with configurable speed, axis, and cleanup
431
+ - ✨ `scrollProgress()` — per-element scroll progress tracking (0→1)
432
+ - ✨ `snapToSection()` — auto-snap to nearest section after scrolling stops
433
+
416
434
  ### v1.1.0
417
435
  - ✨ `scrollSpy()` — highlight nav links by active section
418
436
  - ✨ `onScrollEnd()` — callback when scrolling stops