scroll-snap-kit 1.0.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
@@ -4,6 +4,10 @@
4
4
 
5
5
  Zero dependencies. Tree-shakeable. Works with or without React.
6
6
 
7
+ [![npm version](https://img.shields.io/npm/v/scroll-snap-kit)](https://www.npmjs.com/package/scroll-snap-kit)
8
+ [![license](https://img.shields.io/npm/l/scroll-snap-kit)](./LICENSE)
9
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/scroll-snap-kit)](https://bundlephobia.com/package/scroll-snap-kit)
10
+
7
11
  ---
8
12
 
9
13
  ## Install
@@ -14,27 +18,65 @@ npm install scroll-snap-kit
14
18
 
15
19
  ---
16
20
 
21
+ ## What's included
22
+
23
+ | Utility | Description |
24
+ |---------|-------------|
25
+ | `scrollTo` | Smooth scroll to an element or pixel value |
26
+ | `scrollToTop` / `scrollToBottom` | Page-level scroll helpers |
27
+ | `getScrollPosition` | Current scroll x/y + percentages |
28
+ | `onScroll` | Throttled scroll listener with cleanup |
29
+ | `isInViewport` | Check if an element is visible |
30
+ | `lockScroll` / `unlockScroll` | Freeze body scroll, restore position |
31
+ | `scrollSpy` | Highlight nav links based on active section |
32
+ | `onScrollEnd` | Fire a callback when scrolling stops |
33
+ | `scrollIntoViewIfNeeded` | Only scroll if element is off-screen |
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 |
39
+
40
+ | Hook | Description |
41
+ |------|-------------|
42
+ | `useScrollPosition` | Live scroll position + percentage |
43
+ | `useInViewport` | Whether a ref'd element is visible |
44
+ | `useScrollTo` | Scroll function scoped to a container ref |
45
+ | `useScrolledPast` | Boolean — has user scrolled past a threshold |
46
+ | `useScrollDirection` | `'up'` \| `'down'` \| `null` |
47
+
48
+ ---
49
+
17
50
  ## Vanilla JS Utilities
18
51
 
19
52
  ```js
20
- import { scrollTo, scrollToTop, scrollToBottom, getScrollPosition, onScroll, isInViewport, lockScroll, unlockScroll } from 'scroll-snap-kit/utils';
53
+ import {
54
+ scrollTo, scrollToTop, scrollToBottom,
55
+ getScrollPosition, onScroll, isInViewport,
56
+ lockScroll, unlockScroll,
57
+ scrollSpy, onScrollEnd, scrollIntoViewIfNeeded,
58
+ easeScroll, Easings,
59
+ scrollSequence, parallax, scrollProgress, snapToSection
60
+ } from 'scroll-snap-kit';
21
61
  ```
22
62
 
63
+ ---
64
+
23
65
  ### `scrollTo(target, options?)`
24
66
 
25
67
  Smoothly scroll to a DOM element or a Y pixel value.
26
68
 
27
69
  ```js
28
70
  scrollTo(document.querySelector('#section'));
29
- scrollTo(500); // scroll to y=500px
30
- scrollTo(document.querySelector('#hero'), { offset: -80 }); // with offset (e.g. sticky header)
71
+ scrollTo(500); // scroll to y=500px
72
+ scrollTo(document.querySelector('#hero'), { offset: -80 }); // offset for sticky headers
31
73
  ```
32
74
 
33
75
  | Option | Type | Default | Description |
34
76
  |--------|------|---------|-------------|
35
77
  | `behavior` | `'smooth' \| 'instant'` | `'smooth'` | Scroll behavior |
36
78
  | `block` | `ScrollLogicalPosition` | `'start'` | Vertical alignment |
37
- | `offset` | `number` | `0` | Pixel offset adjustment |
79
+ | `offset` | `number` | `0` | Pixel offset (e.g. `-80` for a sticky nav) |
38
80
 
39
81
  ---
40
82
 
@@ -49,9 +91,13 @@ scrollToBottom({ behavior: 'instant' });
49
91
 
50
92
  ### `getScrollPosition(container?)`
51
93
 
94
+ Returns the current scroll position and scroll percentage for the page or any scrollable container.
95
+
52
96
  ```js
53
97
  const { x, y, percentX, percentY } = getScrollPosition();
54
- // percentY = how far down the page (0–100)
98
+ // percentY how far down the page (0–100)
99
+
100
+ const pos = getScrollPosition(document.querySelector('.sidebar'));
55
101
  ```
56
102
 
57
103
  ---
@@ -61,14 +107,18 @@ const { x, y, percentX, percentY } = getScrollPosition();
61
107
  Throttled scroll listener. Returns a cleanup function.
62
108
 
63
109
  ```js
64
- const stop = onScroll(({ y, percentY }) => {
110
+ const stop = onScroll(({ x, y, percentX, percentY }) => {
65
111
  console.log(`Scrolled ${percentY}% down`);
66
- }, { throttle: 150 });
112
+ }, { throttle: 100 });
67
113
 
68
- // Later:
69
114
  stop(); // removes the listener
70
115
  ```
71
116
 
117
+ | Option | Type | Default | Description |
118
+ |--------|------|---------|-------------|
119
+ | `throttle` | `number` | `100` | Minimum ms between callbacks |
120
+ | `container` | `Element` | `window` | Scrollable container to listen on |
121
+
72
122
  ---
73
123
 
74
124
  ### `isInViewport(element, options?)`
@@ -79,76 +129,259 @@ if (isInViewport(document.querySelector('.card'), { threshold: 0.5 })) {
79
129
  }
80
130
  ```
81
131
 
132
+ | Option | Type | Default | Description |
133
+ |--------|------|---------|-------------|
134
+ | `threshold` | `number` | `0` | 0–1 portion of element that must be visible |
135
+
82
136
  ---
83
137
 
84
138
  ### `lockScroll()` / `unlockScroll()`
85
139
 
86
- Lock page scroll (e.g. when a modal is open) and restore position on unlock.
140
+ ```js
141
+ lockScroll(); // body stops scrolling, position saved
142
+ unlockScroll(); // position restored precisely — no layout jump
143
+ ```
144
+
145
+ ---
146
+
147
+ ### `scrollSpy(sectionsSelector, linksSelector, options?)`
148
+
149
+ Watches scroll position and automatically adds an active class to the nav link matching the current section.
150
+
151
+ ```js
152
+ const stop = scrollSpy(
153
+ 'section[id]',
154
+ 'nav a',
155
+ { offset: 80, activeClass: 'scroll-spy-active' }
156
+ );
157
+
158
+ stop(); // remove listener
159
+ ```
160
+
161
+ ```css
162
+ nav a.scroll-spy-active {
163
+ color: #00ffaa;
164
+ border-bottom: 1px solid currentColor;
165
+ }
166
+ ```
167
+
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 |
172
+
173
+ ---
174
+
175
+ ### `onScrollEnd(callback, options?)`
176
+
177
+ Fires once the user has stopped scrolling for a configurable delay.
178
+
179
+ ```js
180
+ const stop = onScrollEnd(() => {
181
+ console.log('User stopped scrolling!');
182
+ saveScrollPosition();
183
+ }, { delay: 200 });
184
+
185
+ stop();
186
+ ```
187
+
188
+ | Option | Type | Default | Description |
189
+ |--------|------|---------|-------------|
190
+ | `delay` | `number` | `150` | ms of idle scrolling before callback fires |
191
+ | `container` | `Element` | `window` | Scrollable container to watch |
192
+
193
+ ---
194
+
195
+ ### `scrollIntoViewIfNeeded(element, options?)`
196
+
197
+ Scrolls to an element only if it is outside the visible viewport. If it's already visible enough, nothing happens.
198
+
199
+ ```js
200
+ scrollIntoViewIfNeeded(document.querySelector('.card'));
201
+ scrollIntoViewIfNeeded(element, { threshold: 0.5, offset: -80 });
202
+ ```
203
+
204
+ | Option | Type | Default | Description |
205
+ |--------|------|---------|-------------|
206
+ | `threshold` | `number` | `1` | 0–1 visibility ratio required to skip scrolling |
207
+ | `offset` | `number` | `0` | Pixel offset applied when scrolling |
208
+ | `behavior` | `'smooth' \| 'instant'` | `'smooth'` | Scroll behavior |
209
+
210
+ ---
211
+
212
+ ### `easeScroll(target, options?)` + `Easings`
213
+
214
+ Scroll to a position with a fully custom easing curve. Returns a `Promise` that resolves when animation completes.
215
+
216
+ ```js
217
+ await easeScroll('#contact', { duration: 800, easing: Easings.easeOutElastic });
218
+
219
+ // Chain animations
220
+ await easeScroll('#hero', { duration: 600, easing: Easings.easeInOutCubic });
221
+ await easeScroll('#features', { duration: 400, easing: Easings.easeOutQuart });
222
+
223
+ // BYO easing function — any (t: 0→1) => (0→1)
224
+ easeScroll(element, { easing: t => t * t * t });
225
+ ```
226
+
227
+ | Option | Type | Default | Description |
228
+ |--------|------|---------|-------------|
229
+ | `duration` | `number` | `600` | Animation duration in ms |
230
+ | `easing` | `(t: number) => number` | `Easings.easeInOutCubic` | Easing function |
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)`
238
+
239
+ Run multiple `easeScroll` animations one after another as a choreographed sequence. Supports pauses between steps. Returns `{ promise, cancel }`.
240
+
241
+ ```js
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
250
+ ```
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
87
292
 
88
293
  ```js
89
- lockScroll(); // body stops scrolling
90
- unlockScroll(); // restored to previous position
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
91
300
  ```
92
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
+
93
332
  ---
94
333
 
95
334
  ## React Hooks
96
335
 
97
336
  ```js
98
- import { useScrollPosition, useInViewport, useScrollTo, useScrolledPast, useScrollDirection } from 'scroll-snap-kit/hooks';
337
+ import {
338
+ useScrollPosition, useInViewport, useScrollTo,
339
+ useScrolledPast, useScrollDirection
340
+ } from 'scroll-snap-kit/hooks';
99
341
  ```
100
342
 
343
+ > Requires React 16.8+. React is a peer dependency — install it separately.
344
+
345
+ ---
346
+
101
347
  ### `useScrollPosition(options?)`
102
348
 
103
349
  ```jsx
104
350
  function ProgressBar() {
105
351
  const { percentY } = useScrollPosition({ throttle: 50 });
106
- return <div style={{ width: `${percentY}%` }} className="progress" />;
352
+ return <div style={{ width: `${percentY}%` }} className="progress-bar" />;
107
353
  }
108
354
  ```
109
355
 
110
- ---
111
-
112
356
  ### `useInViewport(options?)`
113
357
 
114
358
  ```jsx
115
- function FadeIn() {
359
+ function FadeInCard() {
116
360
  const [ref, inView] = useInViewport({ threshold: 0.2, once: true });
117
361
  return (
118
362
  <div ref={ref} style={{ opacity: inView ? 1 : 0, transition: 'opacity 0.5s' }}>
119
- I fade in when visible!
363
+ Fades in when visible!
120
364
  </div>
121
365
  );
122
366
  }
123
367
  ```
124
368
 
125
- | Option | Type | Default | Description |
126
- |--------|------|---------|-------------|
127
- | `threshold` | `number` | `0` | 0–1 portion of element visible to trigger |
128
- | `once` | `boolean` | `false` | Only trigger the first time |
129
-
130
- ---
131
-
132
369
  ### `useScrollTo()`
133
370
 
134
371
  ```jsx
135
372
  function Page() {
136
373
  const [containerRef, scrollToTarget] = useScrollTo();
137
374
  const sectionRef = useRef(null);
138
-
139
375
  return (
140
376
  <div ref={containerRef} style={{ overflowY: 'scroll', height: '400px' }}>
141
- <button onClick={() => scrollToTarget(sectionRef.current)}>Jump to section</button>
142
- <div style={{ height: 300 }} />
143
- <div ref={sectionRef}>Target section</div>
377
+ <button onClick={() => scrollToTarget(sectionRef.current)}>Jump</button>
378
+ <div ref={sectionRef}>Target</div>
144
379
  </div>
145
380
  );
146
381
  }
147
382
  ```
148
383
 
149
- ---
150
-
151
- ### `useScrolledPast(threshold?, options?)`
384
+ ### `useScrolledPast(threshold?)`
152
385
 
153
386
  ```jsx
154
387
  function BackToTopButton() {
@@ -157,12 +390,10 @@ function BackToTopButton() {
157
390
  }
158
391
  ```
159
392
 
160
- ---
161
-
162
393
  ### `useScrollDirection()`
163
394
 
164
395
  ```jsx
165
- function Navbar() {
396
+ function HideOnScrollNav() {
166
397
  const direction = useScrollDirection();
167
398
  return (
168
399
  <nav style={{ transform: direction === 'down' ? 'translateY(-100%)' : 'translateY(0)' }}>
@@ -172,10 +403,45 @@ function Navbar() {
172
403
  }
173
404
  ```
174
405
 
175
- Returns `'up'`, `'down'`, or `null` (on initial render).
406
+ ---
407
+
408
+ ## Tree-shaking
409
+
410
+ All exports are named and side-effect free:
411
+
412
+ ```js
413
+ import { scrollToTop } from 'scroll-snap-kit'; // ~400 bytes
414
+ import { onScroll, scrollSpy } from 'scroll-snap-kit/utils';
415
+ import { useScrollPosition } from 'scroll-snap-kit/hooks';
416
+ ```
417
+
418
+ ---
419
+
420
+ ## Browser support
421
+
422
+ All modern browsers. `easeScroll` and `scrollSequence` use `requestAnimationFrame`. `useInViewport` / `isInViewport` use `IntersectionObserver` — polyfill if you need IE11.
423
+
424
+ ---
425
+
426
+ ## Changelog
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
+
434
+ ### v1.1.0
435
+ - ✨ `scrollSpy()` — highlight nav links by active section
436
+ - ✨ `onScrollEnd()` — callback when scrolling stops
437
+ - ✨ `scrollIntoViewIfNeeded()` — scroll only when off-screen
438
+ - ✨ `easeScroll()` + `Easings` — custom easing engine with 11 built-in curves
439
+
440
+ ### v1.0.0
441
+ - 🎉 Initial release — 8 core utilities and 5 React hooks
176
442
 
177
443
  ---
178
444
 
179
445
  ## License
180
446
 
181
- MIT
447
+ MIT © Fabian Faraz Farid