@macrulez/inview-core 0.1.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 Danil Lisin
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 ADDED
@@ -0,0 +1,157 @@
1
+ # **Inview Core**
2
+
3
+ ![Inview Core](https://github.com/macrulezru/assets/blob/master/packages-images/inview-vuecraft.png?raw=true)
4
+
5
+ Framework-agnostic engine for scroll tracking, element visibility, and
6
+ viewport-relative position. No Vue, no React — just
7
+ `subscribe`/`unsubscribe` functions that any framework adapter can
8
+ wrap in its own reactivity.
9
+
10
+ Part of the [inview](https://github.com/macrulezru/inview) monorepo.
11
+ Framework adapters:
12
+ [`@macrulez/inview-vue`](https://www.npmjs.com/package/@macrulez/inview-vue),
13
+ [`@macrulez/inview-react`](https://www.npmjs.com/package/@macrulez/inview-react),
14
+ [`@macrulez/inview-nuxt`](https://www.npmjs.com/package/@macrulez/inview-nuxt).
15
+
16
+ ---
17
+
18
+ ## Features
19
+
20
+ - **A scroll engine on one shared rAF loop** — position, direction, progress, and velocity for the window or an element, all derived on one shared `requestAnimationFrame` loop ([`rafLoop`](./src/raf-loop.ts)) instead of a separate one per subscriber
21
+ - **A visibility engine with a pooled `IntersectionObserver`** — enter/leave edge detection and a `once` mode; observers are pooled by `(root, rootMargin, threshold)` ([`ObserverPool`](./src/observer-pool.ts)) instead of one per observed element
22
+ - **Element viewport tracking** — an element's bounding rect, how far it's traveled through the viewport, and its distance from the viewport's center, updated on the shared rAF loop
23
+ - **Standalone utilities** — `mapRange`, `bindCSSVar`, `prefersReducedMotion`, `clamp`, and a set of easing presets, usable on their own
24
+ - **SSR-safe everywhere** — constructing any engine without a `window` returns a no-op object with a static zero-value state instead of throwing
25
+ - **Zero runtime dependencies, zero peer dependencies** — usable standalone in any environment, including outside a framework entirely
26
+
27
+ ---
28
+
29
+ ## When you'd reach for this
30
+
31
+ You're writing a framework adapter of your own, working somewhere Vue/Nuxt/React aren't an option, or just want the raw engines without any reactivity layer on top.
32
+
33
+ - **Building a framework adapter for something not already covered** — Svelte, a vanilla web component, a custom internal framework. The engines expose plain `subscribe`/`unsubscribe` functions with no assumptions about how your reactivity works, so wrapping them is a thin layer, not a rewrite.
34
+ - **A vanilla script with no build step or framework at all** — Not every page needs Vue or React for one scroll-driven effect. `createScrollEngine()` works the same way loaded straight from a `<script type="module">`.
35
+ - **Testing scroll/visibility logic in isolation** — Pass your own `ObserverPool` instance instead of the shared singleton, mock `requestAnimationFrame`, and assert on the engine's state directly, with no component tree in the way.
36
+
37
+ ---
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ npm install @macrulez/inview-core
43
+ ```
44
+
45
+ No peer dependencies. Requires Node.js `18+` if used in a Node/SSR context; in the browser, any environment with `requestAnimationFrame` works, and `IntersectionObserver`-dependent features degrade to no-ops where it's unavailable.
46
+
47
+ ### Quick start
48
+
49
+ ```ts
50
+ import { createScrollEngine } from '@macrulez/inview-core'
51
+
52
+ const engine = createScrollEngine(window, { idleTimeout: 150 })
53
+
54
+ const unsubscribe = engine.subscribe((state) => {
55
+ // state: { x, y, direction, progress, velocity, isScrolling }
56
+ console.log(state.progress) // 0..1 scroll progress of the page
57
+ })
58
+
59
+ engine.scrollTo({ y: 800 }, { behavior: 'smooth' }) // respects prefers-reduced-motion
60
+ unsubscribe()
61
+ engine.destroy() // removes the scroll listener and the rAF subscription
62
+ ```
63
+
64
+ ### More examples
65
+
66
+ #### `createScrollEngine(target?, options?)`
67
+
68
+ Tracks scroll position of `window` (default) or a scrollable `HTMLElement`.
69
+
70
+ | Field | Type | Notes |
71
+ | --- | --- | --- |
72
+ | `x`, `y` | `number` | Current scroll offset |
73
+ | `direction` | `'up' \| 'down' \| 'left' \| 'right' \| null` | Direction of the last scroll delta |
74
+ | `progress` | `number` | 0..1, based on whichever axis (x/y) is actually scrollable |
75
+ | `velocity` | `number` | px moved per animation frame |
76
+ | `isScrolling` | `boolean` | `true` while scrolling, flips back after `idleTimeout` ms (default 150) of inactivity |
77
+
78
+ #### `createVisibilityEngine(pool?)`
79
+
80
+ Thin wrapper around a pooled `IntersectionObserver`, adding enter/leave edge detection and a `once` flag.
81
+
82
+ ```ts
83
+ import { createVisibilityEngine } from '@macrulez/inview-core'
84
+
85
+ const engine = createVisibilityEngine()
86
+
87
+ const stop = engine.observe(
88
+ el,
89
+ { threshold: 0.3, rootMargin: '0px', once: true, onEnter: (info) => console.log(info.edge) },
90
+ (info) => {
91
+ // info: { isIntersecting, intersectionRatio, boundingClientRect, edge }
92
+ },
93
+ )
94
+ ```
95
+
96
+ `edge` is one of `'enter-top' | 'enter-bottom' | 'leave-top' | 'leave-bottom'`, derived from which side of the root the element crossed. Pass your own `ObserverPool` instance (`new ObserverPool()`) if you need isolation from the module-wide default pool — mainly useful in tests.
97
+
98
+ #### `createElementTracker(el)`
99
+
100
+ Tracks an element's position relative to the viewport on the shared rAF loop.
101
+
102
+ ```ts
103
+ import { createElementTracker } from '@macrulez/inview-core'
104
+
105
+ const tracker = createElementTracker(el)
106
+ tracker.subscribe((state) => {
107
+ // state: { rect, viewportProgress, distanceFromCenter }
108
+ })
109
+ ```
110
+
111
+ - `rect` — a plain `{ top, left, right, bottom, width, height }` snapshot of `getBoundingClientRect()`.
112
+ - `viewportProgress` — 0..1, from the element entering at the viewport's bottom edge to leaving at its top edge. The core value parallax effects are built on.
113
+ - `distanceFromCenter` — px offset between the element's center and the viewport's center (negative = above center).
114
+
115
+ #### Utilities
116
+
117
+ ```ts
118
+ import { mapRange, bindCSSVar, prefersReducedMotion, easings, clamp } from '@macrulez/inview-core'
119
+
120
+ mapRange(0.5, [0, 1], [0, 100]) // 50, linear remap
121
+ mapRange(1.4, [0, 1], [0, 100], true) // 100, clamped to the output range
122
+
123
+ bindCSSVar(el, '--p', 0.42) // el.style.setProperty('--p', '0.42')
124
+
125
+ prefersReducedMotion() // matches (prefers-reduced-motion: reduce)
126
+
127
+ easings.easeInOutQuad(0.5) // 0.5 — see src/easing.ts for the full preset list
128
+ ```
129
+
130
+ ---
131
+
132
+ ## Documentation & links
133
+
134
+ - 📖 **Full documentation:** [npm.vuecraft.ru/en/packages/inview](https://npm.vuecraft.ru/en/packages/inview/guide/overview.html)
135
+ - 🌐 **VueCraft:** [vuecraft.ru/en](https://vuecraft.ru/en)
136
+ - 👤 **Author:** [macrulez.ru/en](https://macrulez.ru/en)
137
+ - 💻 **GitHub:** [macrulezru/inview/packages/core](https://github.com/macrulezru/inview/tree/master/packages/core)
138
+ - 📦 **NPM:** [@macrulez/inview-core](https://www.npmjs.com/package/@macrulez/inview-core)
139
+ - 🐛 **Issues:** [github.com/macrulezru/inview/issues](https://github.com/macrulezru/inview/issues)
140
+
141
+ ---
142
+
143
+ ## License
144
+
145
+ MIT
146
+
147
+ ---
148
+
149
+ ## 💖 Support the project
150
+
151
+ Open source takes time and effort. If this library saves you time or brings value, consider supporting further development.
152
+
153
+ <a href="https://donate.cryptocloud.plus/M6O34NIN" target="_blank">
154
+ <img src="https://img.shields.io/badge/Donate-CryptoCloud-8A2BE2?style=for-the-badge&logo=cryptocurrency&logoColor=white" alt="Donate via CryptoCloud">
155
+ </a>
156
+
157
+ Thank you for being part of this journey. ❤️
package/dist/index.cjs ADDED
@@ -0,0 +1,416 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ObserverPool: () => ObserverPool,
24
+ bindCSSVar: () => bindCSSVar,
25
+ clamp: () => clamp,
26
+ createElementTracker: () => createElementTracker,
27
+ createScrollEngine: () => createScrollEngine,
28
+ createVisibilityEngine: () => createVisibilityEngine,
29
+ easings: () => easings,
30
+ mapRange: () => mapRange,
31
+ observerPool: () => observerPool,
32
+ prefersReducedMotion: () => prefersReducedMotion,
33
+ rafLoop: () => rafLoop
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+
37
+ // src/raf-loop.ts
38
+ var RafLoop = class {
39
+ constructor() {
40
+ this.callbacks = /* @__PURE__ */ new Set();
41
+ this.handle = null;
42
+ }
43
+ add(cb) {
44
+ this.callbacks.add(cb);
45
+ this.start();
46
+ return () => {
47
+ this.callbacks.delete(cb);
48
+ if (this.callbacks.size === 0) this.stop();
49
+ };
50
+ }
51
+ start() {
52
+ if (this.handle !== null || typeof requestAnimationFrame === "undefined") return;
53
+ const loop = (time) => {
54
+ this.callbacks.forEach((cb) => cb(time));
55
+ if (this.callbacks.size > 0) {
56
+ this.handle = requestAnimationFrame(loop);
57
+ } else {
58
+ this.handle = null;
59
+ }
60
+ };
61
+ this.handle = requestAnimationFrame(loop);
62
+ }
63
+ stop() {
64
+ if (this.handle !== null && typeof cancelAnimationFrame !== "undefined") {
65
+ cancelAnimationFrame(this.handle);
66
+ }
67
+ this.handle = null;
68
+ }
69
+ };
70
+ var rafLoop = new RafLoop();
71
+
72
+ // src/utils/clamp.ts
73
+ function clamp(value, min, max) {
74
+ return Math.min(max, Math.max(min, value));
75
+ }
76
+
77
+ // src/utils/prefersReducedMotion.ts
78
+ function prefersReducedMotion() {
79
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
80
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
81
+ }
82
+
83
+ // src/scroll-engine.ts
84
+ function isWindowTarget(target) {
85
+ return typeof window !== "undefined" && target === window;
86
+ }
87
+ function getScrollPosition(target) {
88
+ if (isWindowTarget(target)) return { x: window.scrollX, y: window.scrollY };
89
+ return { x: target.scrollLeft, y: target.scrollTop };
90
+ }
91
+ function getMaxScroll(target) {
92
+ if (isWindowTarget(target)) {
93
+ const doc = document.documentElement;
94
+ return {
95
+ x: Math.max(doc.scrollWidth - window.innerWidth, 0),
96
+ y: Math.max(doc.scrollHeight - window.innerHeight, 0)
97
+ };
98
+ }
99
+ return {
100
+ x: Math.max(target.scrollWidth - target.clientWidth, 0),
101
+ y: Math.max(target.scrollHeight - target.clientHeight, 0)
102
+ };
103
+ }
104
+ function computeProgress(target, x, y) {
105
+ const max = getMaxScroll(target);
106
+ if (max.y > 0) return clamp(y / max.y, 0, 1);
107
+ if (max.x > 0) return clamp(x / max.x, 0, 1);
108
+ return 0;
109
+ }
110
+ function createNoopEngine() {
111
+ const state = { x: 0, y: 0, direction: null, progress: 0, velocity: 0, isScrolling: false };
112
+ return {
113
+ getState: () => state,
114
+ subscribe: (cb) => {
115
+ cb(state);
116
+ return () => {
117
+ };
118
+ },
119
+ scrollTo: () => {
120
+ },
121
+ destroy: () => {
122
+ }
123
+ };
124
+ }
125
+ function createScrollEngine(target = typeof window !== "undefined" ? window : void 0, options = {}) {
126
+ if (typeof window === "undefined" || !target) return createNoopEngine();
127
+ const idleTimeout = options.idleTimeout ?? 150;
128
+ const subscribers = /* @__PURE__ */ new Set();
129
+ const initial = getScrollPosition(target);
130
+ let state = {
131
+ x: initial.x,
132
+ y: initial.y,
133
+ direction: null,
134
+ progress: computeProgress(target, initial.x, initial.y),
135
+ velocity: 0,
136
+ isScrolling: false
137
+ };
138
+ let frameLastX = initial.x;
139
+ let frameLastY = initial.y;
140
+ let idleHandle = null;
141
+ function notify() {
142
+ subscribers.forEach((cb) => cb(state));
143
+ }
144
+ function onScroll() {
145
+ const pos = getScrollPosition(target);
146
+ const dx = pos.x - state.x;
147
+ const dy = pos.y - state.y;
148
+ let direction = state.direction;
149
+ if (dy > 0) direction = "down";
150
+ else if (dy < 0) direction = "up";
151
+ else if (dx > 0) direction = "right";
152
+ else if (dx < 0) direction = "left";
153
+ state = {
154
+ ...state,
155
+ x: pos.x,
156
+ y: pos.y,
157
+ direction,
158
+ progress: computeProgress(target, pos.x, pos.y),
159
+ isScrolling: true
160
+ };
161
+ if (idleHandle !== null) clearTimeout(idleHandle);
162
+ idleHandle = setTimeout(() => {
163
+ idleHandle = null;
164
+ state = { ...state, isScrolling: false, velocity: 0 };
165
+ notify();
166
+ }, idleTimeout);
167
+ notify();
168
+ }
169
+ target.addEventListener("scroll", onScroll, { passive: true });
170
+ const removeRaf = rafLoop.add(() => {
171
+ const velocity = Math.hypot(state.x - frameLastX, state.y - frameLastY);
172
+ frameLastX = state.x;
173
+ frameLastY = state.y;
174
+ if (velocity !== state.velocity) {
175
+ state = { ...state, velocity };
176
+ notify();
177
+ }
178
+ });
179
+ function scrollTo(pos, opts = {}) {
180
+ const behavior = opts.behavior ?? (prefersReducedMotion() ? "auto" : "smooth");
181
+ const left = typeof pos === "number" ? void 0 : pos.x;
182
+ const top = typeof pos === "number" ? pos : pos.y;
183
+ target.scrollTo({ left, top, behavior });
184
+ }
185
+ return {
186
+ getState: () => state,
187
+ subscribe(cb) {
188
+ subscribers.add(cb);
189
+ cb(state);
190
+ return () => subscribers.delete(cb);
191
+ },
192
+ scrollTo,
193
+ destroy() {
194
+ target.removeEventListener("scroll", onScroll);
195
+ if (idleHandle !== null) clearTimeout(idleHandle);
196
+ removeRaf();
197
+ subscribers.clear();
198
+ }
199
+ };
200
+ }
201
+
202
+ // src/observer-pool.ts
203
+ function keyFor(options) {
204
+ const threshold = Array.isArray(options.threshold) ? options.threshold.join(",") : String(options.threshold ?? 0);
205
+ const root = options.root ? "r" : "window";
206
+ return `${root}|${options.rootMargin ?? "0px"}|${threshold}`;
207
+ }
208
+ var ObserverPool = class {
209
+ constructor() {
210
+ this.pools = /* @__PURE__ */ new Map();
211
+ }
212
+ observe(el, options, cb) {
213
+ if (typeof IntersectionObserver === "undefined") return () => {
214
+ };
215
+ const key = keyFor(options);
216
+ let pool = this.pools.get(key);
217
+ if (!pool) {
218
+ const callbacks2 = /* @__PURE__ */ new Map();
219
+ const observer = new IntersectionObserver(
220
+ (entries) => {
221
+ for (const entry of entries) {
222
+ callbacks2.get(entry.target)?.forEach((fn) => fn(entry));
223
+ }
224
+ },
225
+ {
226
+ root: options.root ?? null,
227
+ rootMargin: options.rootMargin ?? "0px",
228
+ threshold: options.threshold ?? 0
229
+ }
230
+ );
231
+ pool = { observer, callbacks: callbacks2 };
232
+ this.pools.set(key, pool);
233
+ }
234
+ let callbacks = pool.callbacks.get(el);
235
+ if (!callbacks) {
236
+ callbacks = /* @__PURE__ */ new Set();
237
+ pool.callbacks.set(el, callbacks);
238
+ }
239
+ const isNewElement = callbacks.size === 0;
240
+ callbacks.add(cb);
241
+ if (isNewElement) pool.observer.observe(el);
242
+ const currentPool = pool;
243
+ return () => {
244
+ const set = currentPool.callbacks.get(el);
245
+ if (!set) return;
246
+ set.delete(cb);
247
+ if (set.size === 0) {
248
+ currentPool.callbacks.delete(el);
249
+ currentPool.observer.unobserve(el);
250
+ }
251
+ if (currentPool.callbacks.size === 0) {
252
+ currentPool.observer.disconnect();
253
+ this.pools.delete(key);
254
+ }
255
+ };
256
+ }
257
+ };
258
+ var observerPool = new ObserverPool();
259
+
260
+ // src/visibility-engine.ts
261
+ function computeEdge(entry, wasIntersecting) {
262
+ if (entry.isIntersecting === wasIntersecting) return void 0;
263
+ const rootTop = entry.rootBounds ? entry.rootBounds.top : 0;
264
+ const enteringOrLeavingFromBottom = entry.boundingClientRect.top > rootTop;
265
+ if (entry.isIntersecting) return enteringOrLeavingFromBottom ? "enter-bottom" : "enter-top";
266
+ return enteringOrLeavingFromBottom ? "leave-bottom" : "leave-top";
267
+ }
268
+ function createVisibilityEngine(pool = observerPool) {
269
+ const unsubs = /* @__PURE__ */ new Set();
270
+ function observe(el, options, cb) {
271
+ let wasIntersecting = false;
272
+ const unobserve = pool.observe(el, options, (entry) => {
273
+ const edge = computeEdge(entry, wasIntersecting);
274
+ wasIntersecting = entry.isIntersecting;
275
+ const info = {
276
+ isIntersecting: entry.isIntersecting,
277
+ intersectionRatio: entry.intersectionRatio,
278
+ boundingClientRect: entry.boundingClientRect,
279
+ edge
280
+ };
281
+ cb(info);
282
+ if (edge === "enter-top" || edge === "enter-bottom") options.onEnter?.(info);
283
+ if (edge === "leave-top" || edge === "leave-bottom") options.onLeave?.(info);
284
+ if (options.once && entry.isIntersecting) {
285
+ unsubs.delete(unobserve);
286
+ unobserve();
287
+ }
288
+ });
289
+ unsubs.add(unobserve);
290
+ return () => {
291
+ unsubs.delete(unobserve);
292
+ unobserve();
293
+ };
294
+ }
295
+ return {
296
+ observe,
297
+ destroy() {
298
+ unsubs.forEach((fn) => fn());
299
+ unsubs.clear();
300
+ }
301
+ };
302
+ }
303
+
304
+ // src/element-tracker.ts
305
+ function toDomRectLike(rect) {
306
+ return {
307
+ top: rect.top,
308
+ left: rect.left,
309
+ right: rect.right,
310
+ bottom: rect.bottom,
311
+ width: rect.width,
312
+ height: rect.height
313
+ };
314
+ }
315
+ var emptyRect = { top: 0, left: 0, right: 0, bottom: 0, width: 0, height: 0 };
316
+ function computeViewportProgress(rect, viewportHeight) {
317
+ const total = viewportHeight + rect.height;
318
+ if (total <= 0) return 0;
319
+ const traveled = viewportHeight - rect.top;
320
+ return clamp(traveled / total, 0, 1);
321
+ }
322
+ function computeState(el) {
323
+ const rect = el.getBoundingClientRect();
324
+ const viewportHeight = window.innerHeight;
325
+ const elementCenter = rect.top + rect.height / 2;
326
+ const viewportCenter = viewportHeight / 2;
327
+ return {
328
+ rect: toDomRectLike(rect),
329
+ viewportProgress: computeViewportProgress(rect, viewportHeight),
330
+ distanceFromCenter: elementCenter - viewportCenter
331
+ };
332
+ }
333
+ function statesEqual(a, b) {
334
+ return a.rect.top === b.rect.top && a.rect.left === b.rect.left && a.rect.width === b.rect.width && a.rect.height === b.rect.height && a.viewportProgress === b.viewportProgress && a.distanceFromCenter === b.distanceFromCenter;
335
+ }
336
+ function createElementTracker(el) {
337
+ if (typeof window === "undefined") {
338
+ const state2 = { rect: emptyRect, viewportProgress: 0, distanceFromCenter: 0 };
339
+ return {
340
+ getState: () => state2,
341
+ subscribe: (cb) => {
342
+ cb(state2);
343
+ return () => {
344
+ };
345
+ },
346
+ destroy: () => {
347
+ }
348
+ };
349
+ }
350
+ const subscribers = /* @__PURE__ */ new Set();
351
+ let state = computeState(el);
352
+ const removeRaf = rafLoop.add(() => {
353
+ const next = computeState(el);
354
+ if (!statesEqual(next, state)) {
355
+ state = next;
356
+ subscribers.forEach((cb) => cb(state));
357
+ }
358
+ });
359
+ return {
360
+ getState: () => state,
361
+ subscribe(cb) {
362
+ subscribers.add(cb);
363
+ cb(state);
364
+ return () => subscribers.delete(cb);
365
+ },
366
+ destroy() {
367
+ removeRaf();
368
+ subscribers.clear();
369
+ }
370
+ };
371
+ }
372
+
373
+ // src/utils/mapRange.ts
374
+ function mapRange(value, from, to, clampResult = false) {
375
+ const [inMin, inMax] = from;
376
+ const [outMin, outMax] = to;
377
+ const ratio = inMax === inMin ? 0 : (value - inMin) / (inMax - inMin);
378
+ const result = outMin + ratio * (outMax - outMin);
379
+ if (!clampResult) return result;
380
+ return clamp(result, Math.min(outMin, outMax), Math.max(outMin, outMax));
381
+ }
382
+
383
+ // src/utils/bindCSSVar.ts
384
+ function bindCSSVar(el, name, value) {
385
+ const varName = name.startsWith("--") ? name : `--${name}`;
386
+ el.style.setProperty(varName, String(value));
387
+ }
388
+
389
+ // src/easing.ts
390
+ var easings = {
391
+ linear: (t) => t,
392
+ easeInQuad: (t) => t * t,
393
+ easeOutQuad: (t) => t * (2 - t),
394
+ easeInOutQuad: (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
395
+ easeInCubic: (t) => t * t * t,
396
+ easeOutCubic: (t) => 1 - Math.pow(1 - t, 3),
397
+ easeInOutCubic: (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2,
398
+ easeInSine: (t) => 1 - Math.cos(t * Math.PI / 2),
399
+ easeOutSine: (t) => Math.sin(t * Math.PI / 2),
400
+ easeInOutSine: (t) => -(Math.cos(Math.PI * t) - 1) / 2
401
+ };
402
+ // Annotate the CommonJS export names for ESM import in node:
403
+ 0 && (module.exports = {
404
+ ObserverPool,
405
+ bindCSSVar,
406
+ clamp,
407
+ createElementTracker,
408
+ createScrollEngine,
409
+ createVisibilityEngine,
410
+ easings,
411
+ mapRange,
412
+ observerPool,
413
+ prefersReducedMotion,
414
+ rafLoop
415
+ });
416
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/raf-loop.ts","../src/utils/clamp.ts","../src/utils/prefersReducedMotion.ts","../src/scroll-engine.ts","../src/observer-pool.ts","../src/visibility-engine.ts","../src/element-tracker.ts","../src/utils/mapRange.ts","../src/utils/bindCSSVar.ts","../src/easing.ts"],"sourcesContent":["export { createScrollEngine } from './scroll-engine'\nexport { createVisibilityEngine } from './visibility-engine'\nexport { createElementTracker } from './element-tracker'\nexport { observerPool, ObserverPool } from './observer-pool'\nexport { rafLoop } from './raf-loop'\n\nexport { mapRange } from './utils/mapRange'\nexport { bindCSSVar } from './utils/bindCSSVar'\nexport { prefersReducedMotion } from './utils/prefersReducedMotion'\nexport { clamp } from './utils/clamp'\nexport { easings } from './easing'\nexport type { Easing, EasingName } from './easing'\n\nexport type {\n ScrollDirection,\n ScrollState,\n ScrollToOptions,\n ScrollEngineOptions,\n ScrollEngine,\n IntersectionEdge,\n IntersectionInfo,\n ObserverPoolOptions,\n VisibilityObserveOptions,\n VisibilityEngine,\n DOMRectLike,\n ElementTrackerState,\n ElementTracker,\n} from './types'\n","type Tick = (time: number) => void\n\n/**\n * One shared requestAnimationFrame loop for the whole engine, instead of\n * every scroll/tracker subscription running its own rAF callback.\n */\nclass RafLoop {\n private callbacks = new Set<Tick>()\n private handle: number | null = null\n\n add(cb: Tick): () => void {\n this.callbacks.add(cb)\n this.start()\n return () => {\n this.callbacks.delete(cb)\n if (this.callbacks.size === 0) this.stop()\n }\n }\n\n private start() {\n if (this.handle !== null || typeof requestAnimationFrame === 'undefined') return\n const loop: FrameRequestCallback = (time) => {\n this.callbacks.forEach((cb) => cb(time))\n if (this.callbacks.size > 0) {\n this.handle = requestAnimationFrame(loop)\n } else {\n this.handle = null\n }\n }\n this.handle = requestAnimationFrame(loop)\n }\n\n private stop() {\n if (this.handle !== null && typeof cancelAnimationFrame !== 'undefined') {\n cancelAnimationFrame(this.handle)\n }\n this.handle = null\n }\n}\n\nexport const rafLoop = new RafLoop()\n","export function clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value))\n}\n","export function prefersReducedMotion(): boolean {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return false\n return window.matchMedia('(prefers-reduced-motion: reduce)').matches\n}\n","import { rafLoop } from './raf-loop'\nimport { clamp } from './utils/clamp'\nimport { prefersReducedMotion } from './utils/prefersReducedMotion'\nimport type { ScrollDirection, ScrollEngine, ScrollEngineOptions, ScrollState, ScrollToOptions } from './types'\n\ntype ScrollTarget = Window | HTMLElement\n\nfunction isWindowTarget(target: ScrollTarget): target is Window {\n return typeof window !== 'undefined' && target === window\n}\n\nfunction getScrollPosition(target: ScrollTarget) {\n if (isWindowTarget(target)) return { x: window.scrollX, y: window.scrollY }\n return { x: target.scrollLeft, y: target.scrollTop }\n}\n\nfunction getMaxScroll(target: ScrollTarget) {\n if (isWindowTarget(target)) {\n const doc = document.documentElement\n return {\n x: Math.max(doc.scrollWidth - window.innerWidth, 0),\n y: Math.max(doc.scrollHeight - window.innerHeight, 0),\n }\n }\n return {\n x: Math.max(target.scrollWidth - target.clientWidth, 0),\n y: Math.max(target.scrollHeight - target.clientHeight, 0),\n }\n}\n\nfunction computeProgress(target: ScrollTarget, x: number, y: number): number {\n const max = getMaxScroll(target)\n if (max.y > 0) return clamp(y / max.y, 0, 1)\n if (max.x > 0) return clamp(x / max.x, 0, 1)\n return 0\n}\n\nfunction createNoopEngine(): ScrollEngine {\n const state: ScrollState = { x: 0, y: 0, direction: null, progress: 0, velocity: 0, isScrolling: false }\n return {\n getState: () => state,\n subscribe: (cb) => {\n cb(state)\n return () => {}\n },\n scrollTo: () => {},\n destroy: () => {},\n }\n}\n\n/**\n * Framework-agnostic scroll engine for window or a scrollable element.\n * Batches DOM scroll events into one shared rAF loop and notifies\n * subscribers only when the derived state actually changes.\n */\nexport function createScrollEngine(\n target: ScrollTarget = typeof window !== 'undefined' ? window : (undefined as unknown as Window),\n options: ScrollEngineOptions = {}\n): ScrollEngine {\n if (typeof window === 'undefined' || !target) return createNoopEngine()\n\n const idleTimeout = options.idleTimeout ?? 150\n const subscribers = new Set<(state: ScrollState) => void>()\n\n const initial = getScrollPosition(target)\n let state: ScrollState = {\n x: initial.x,\n y: initial.y,\n direction: null,\n progress: computeProgress(target, initial.x, initial.y),\n velocity: 0,\n isScrolling: false,\n }\n\n let frameLastX = initial.x\n let frameLastY = initial.y\n let idleHandle: ReturnType<typeof setTimeout> | null = null\n\n function notify() {\n subscribers.forEach((cb) => cb(state))\n }\n\n function onScroll() {\n const pos = getScrollPosition(target)\n const dx = pos.x - state.x\n const dy = pos.y - state.y\n\n let direction: ScrollDirection = state.direction\n if (dy > 0) direction = 'down'\n else if (dy < 0) direction = 'up'\n else if (dx > 0) direction = 'right'\n else if (dx < 0) direction = 'left'\n\n state = {\n ...state,\n x: pos.x,\n y: pos.y,\n direction,\n progress: computeProgress(target, pos.x, pos.y),\n isScrolling: true,\n }\n\n if (idleHandle !== null) clearTimeout(idleHandle)\n idleHandle = setTimeout(() => {\n idleHandle = null\n state = { ...state, isScrolling: false, velocity: 0 }\n notify()\n }, idleTimeout)\n\n notify()\n }\n\n target.addEventListener('scroll', onScroll, { passive: true })\n\n const removeRaf = rafLoop.add(() => {\n const velocity = Math.hypot(state.x - frameLastX, state.y - frameLastY)\n frameLastX = state.x\n frameLastY = state.y\n if (velocity !== state.velocity) {\n state = { ...state, velocity }\n notify()\n }\n })\n\n function scrollTo(pos: number | { x?: number; y?: number }, opts: ScrollToOptions = {}) {\n const behavior = opts.behavior ?? (prefersReducedMotion() ? 'auto' : 'smooth')\n const left = typeof pos === 'number' ? undefined : pos.x\n const top = typeof pos === 'number' ? pos : pos.y\n target.scrollTo({ left, top, behavior })\n }\n\n return {\n getState: () => state,\n subscribe(cb) {\n subscribers.add(cb)\n cb(state)\n return () => subscribers.delete(cb)\n },\n scrollTo,\n destroy() {\n target.removeEventListener('scroll', onScroll)\n if (idleHandle !== null) clearTimeout(idleHandle)\n removeRaf()\n subscribers.clear()\n },\n }\n}\n","import type { ObserverPoolOptions } from './types'\n\ntype EntryCallback = (entry: IntersectionObserverEntry) => void\n\ninterface Pool {\n observer: IntersectionObserver\n callbacks: Map<Element, Set<EntryCallback>>\n}\n\nfunction keyFor(options: ObserverPoolOptions): string {\n const threshold = Array.isArray(options.threshold) ? options.threshold.join(',') : String(options.threshold ?? 0)\n const root = options.root ? 'r' : 'window'\n return `${root}|${options.rootMargin ?? '0px'}|${threshold}`\n}\n\n/**\n * Pools IntersectionObserver instances by (root, rootMargin, threshold),\n * so many observed elements sharing the same options share one observer\n * instead of spawning one per element.\n */\nexport class ObserverPool {\n private pools = new Map<string, Pool>()\n\n observe(el: Element, options: ObserverPoolOptions, cb: EntryCallback): () => void {\n if (typeof IntersectionObserver === 'undefined') return () => {}\n\n const key = keyFor(options)\n let pool = this.pools.get(key)\n if (!pool) {\n const callbacks = new Map<Element, Set<EntryCallback>>()\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n callbacks.get(entry.target)?.forEach((fn) => fn(entry))\n }\n },\n {\n root: options.root ?? null,\n rootMargin: options.rootMargin ?? '0px',\n threshold: options.threshold ?? 0,\n }\n )\n pool = { observer, callbacks }\n this.pools.set(key, pool)\n }\n\n let callbacks = pool.callbacks.get(el)\n if (!callbacks) {\n callbacks = new Set()\n pool.callbacks.set(el, callbacks)\n }\n const isNewElement = callbacks.size === 0\n callbacks.add(cb)\n if (isNewElement) pool.observer.observe(el)\n\n const currentPool = pool\n return () => {\n const set = currentPool.callbacks.get(el)\n if (!set) return\n set.delete(cb)\n if (set.size === 0) {\n currentPool.callbacks.delete(el)\n currentPool.observer.unobserve(el)\n }\n if (currentPool.callbacks.size === 0) {\n currentPool.observer.disconnect()\n this.pools.delete(key)\n }\n }\n }\n}\n\nexport const observerPool = new ObserverPool()\n","import { observerPool as defaultObserverPool, ObserverPool } from './observer-pool'\nimport type { IntersectionEdge, IntersectionInfo, VisibilityEngine, VisibilityObserveOptions } from './types'\n\nfunction computeEdge(entry: IntersectionObserverEntry, wasIntersecting: boolean): IntersectionEdge | undefined {\n if (entry.isIntersecting === wasIntersecting) return undefined\n const rootTop = entry.rootBounds ? entry.rootBounds.top : 0\n const enteringOrLeavingFromBottom = entry.boundingClientRect.top > rootTop\n if (entry.isIntersecting) return enteringOrLeavingFromBottom ? 'enter-bottom' : 'enter-top'\n return enteringOrLeavingFromBottom ? 'leave-bottom' : 'leave-top'\n}\n\n/**\n * Wraps the shared observer pool with enter/leave edge detection and\n * `once` support, without any framework-specific reactivity.\n */\nexport function createVisibilityEngine(pool: ObserverPool = defaultObserverPool): VisibilityEngine {\n const unsubs = new Set<() => void>()\n\n function observe(el: Element, options: VisibilityObserveOptions, cb: (info: IntersectionInfo) => void) {\n let wasIntersecting = false\n\n const unobserve = pool.observe(el, options, (entry) => {\n const edge = computeEdge(entry, wasIntersecting)\n wasIntersecting = entry.isIntersecting\n\n const info: IntersectionInfo = {\n isIntersecting: entry.isIntersecting,\n intersectionRatio: entry.intersectionRatio,\n boundingClientRect: entry.boundingClientRect,\n edge,\n }\n\n cb(info)\n if (edge === 'enter-top' || edge === 'enter-bottom') options.onEnter?.(info)\n if (edge === 'leave-top' || edge === 'leave-bottom') options.onLeave?.(info)\n\n if (options.once && entry.isIntersecting) {\n unsubs.delete(unobserve)\n unobserve()\n }\n })\n\n unsubs.add(unobserve)\n return () => {\n unsubs.delete(unobserve)\n unobserve()\n }\n }\n\n return {\n observe,\n destroy() {\n unsubs.forEach((fn) => fn())\n unsubs.clear()\n },\n }\n}\n","import { rafLoop } from './raf-loop'\nimport { clamp } from './utils/clamp'\nimport type { DOMRectLike, ElementTracker, ElementTrackerState } from './types'\n\nfunction toDomRectLike(rect: DOMRect): DOMRectLike {\n return {\n top: rect.top,\n left: rect.left,\n right: rect.right,\n bottom: rect.bottom,\n width: rect.width,\n height: rect.height,\n }\n}\n\nconst emptyRect: DOMRectLike = { top: 0, left: 0, right: 0, bottom: 0, width: 0, height: 0 }\n\nfunction computeViewportProgress(rect: DOMRect, viewportHeight: number): number {\n const total = viewportHeight + rect.height\n if (total <= 0) return 0\n const traveled = viewportHeight - rect.top\n return clamp(traveled / total, 0, 1)\n}\n\nfunction computeState(el: HTMLElement): ElementTrackerState {\n const rect = el.getBoundingClientRect()\n const viewportHeight = window.innerHeight\n const elementCenter = rect.top + rect.height / 2\n const viewportCenter = viewportHeight / 2\n return {\n rect: toDomRectLike(rect),\n viewportProgress: computeViewportProgress(rect, viewportHeight),\n distanceFromCenter: elementCenter - viewportCenter,\n }\n}\n\nfunction statesEqual(a: ElementTrackerState, b: ElementTrackerState): boolean {\n return (\n a.rect.top === b.rect.top &&\n a.rect.left === b.rect.left &&\n a.rect.width === b.rect.width &&\n a.rect.height === b.rect.height &&\n a.viewportProgress === b.viewportProgress &&\n a.distanceFromCenter === b.distanceFromCenter\n )\n}\n\n/**\n * Tracks an element's position relative to the viewport (rect,\n * viewportProgress, distanceFromCenter) on the shared rAF loop.\n */\nexport function createElementTracker(el: HTMLElement): ElementTracker {\n if (typeof window === 'undefined') {\n const state: ElementTrackerState = { rect: emptyRect, viewportProgress: 0, distanceFromCenter: 0 }\n return {\n getState: () => state,\n subscribe: (cb) => {\n cb(state)\n return () => {}\n },\n destroy: () => {},\n }\n }\n\n const subscribers = new Set<(state: ElementTrackerState) => void>()\n let state = computeState(el)\n\n const removeRaf = rafLoop.add(() => {\n const next = computeState(el)\n if (!statesEqual(next, state)) {\n state = next\n subscribers.forEach((cb) => cb(state))\n }\n })\n\n return {\n getState: () => state,\n subscribe(cb) {\n subscribers.add(cb)\n cb(state)\n return () => subscribers.delete(cb)\n },\n destroy() {\n removeRaf()\n subscribers.clear()\n },\n }\n}\n","import { clamp } from './clamp'\n\n/**\n * Linearly maps `value` from the `from` range into the `to` range.\n * Pass `clampResult: true` to keep the result within `to` at the edges.\n */\nexport function mapRange(\n value: number,\n from: [number, number],\n to: [number, number],\n clampResult = false\n): number {\n const [inMin, inMax] = from\n const [outMin, outMax] = to\n const ratio = inMax === inMin ? 0 : (value - inMin) / (inMax - inMin)\n const result = outMin + ratio * (outMax - outMin)\n if (!clampResult) return result\n return clamp(result, Math.min(outMin, outMax), Math.max(outMin, outMax))\n}\n","/**\n * Writes a numeric progress value into a CSS custom property on `el`,\n * so the resulting visual effect can be composed in plain CSS.\n */\nexport function bindCSSVar(el: HTMLElement, name: string, value: number | string): void {\n const varName = name.startsWith('--') ? name : `--${name}`\n el.style.setProperty(varName, String(value))\n}\n","export type Easing = (t: number) => number\n\n/**\n * Standard easing presets to apply to a 0..1 progress value before\n * feeding it into an effect (parallax offset, CSS var, etc).\n */\nexport const easings = {\n linear: (t: number) => t,\n\n easeInQuad: (t: number) => t * t,\n easeOutQuad: (t: number) => t * (2 - t),\n easeInOutQuad: (t: number) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),\n\n easeInCubic: (t: number) => t * t * t,\n easeOutCubic: (t: number) => 1 - Math.pow(1 - t, 3),\n easeInOutCubic: (t: number) => (t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2),\n\n easeInSine: (t: number) => 1 - Math.cos((t * Math.PI) / 2),\n easeOutSine: (t: number) => Math.sin((t * Math.PI) / 2),\n easeInOutSine: (t: number) => -(Math.cos(Math.PI * t) - 1) / 2,\n} satisfies Record<string, Easing>\n\nexport type EasingName = keyof typeof easings\n"],"mappings":"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","names":["callbacks","state"]}