use-scroll-animate 1.2.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/dist/index.js ADDED
@@ -0,0 +1,708 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ /**
6
+ * use-scroll-animate - Animation Presets
7
+ * Defines keyframes for all built-in animation presets
8
+ */
9
+ const PRESETS = {
10
+ 'fade-in': {
11
+ from: { opacity: 0 },
12
+ to: { opacity: 1 },
13
+ },
14
+ 'fade-in-up': {
15
+ from: { opacity: 0, transform: 'translateY(40px)' },
16
+ to: { opacity: 1, transform: 'translateY(0)' },
17
+ },
18
+ 'fade-in-down': {
19
+ from: { opacity: 0, transform: 'translateY(-40px)' },
20
+ to: { opacity: 1, transform: 'translateY(0)' },
21
+ },
22
+ 'fade-in-left': {
23
+ from: { opacity: 0, transform: 'translateX(-40px)' },
24
+ to: { opacity: 1, transform: 'translateX(0)' },
25
+ },
26
+ 'fade-in-right': {
27
+ from: { opacity: 0, transform: 'translateX(40px)' },
28
+ to: { opacity: 1, transform: 'translateX(0)' },
29
+ },
30
+ 'zoom-in': {
31
+ from: { opacity: 0, transform: 'scale(0.8)' },
32
+ to: { opacity: 1, transform: 'scale(1)' },
33
+ },
34
+ 'zoom-out': {
35
+ from: { opacity: 0, transform: 'scale(1.2)' },
36
+ to: { opacity: 1, transform: 'scale(1)' },
37
+ },
38
+ 'flip-x': {
39
+ from: { opacity: 0, transform: 'rotateX(-90deg)' },
40
+ to: { opacity: 1, transform: 'rotateX(0)' },
41
+ },
42
+ 'flip-y': {
43
+ from: { opacity: 0, transform: 'rotateY(-90deg)' },
44
+ to: { opacity: 1, transform: 'rotateY(0)' },
45
+ },
46
+ 'slide-up': {
47
+ from: { transform: 'translateY(100%)' },
48
+ to: { transform: 'translateY(0)' },
49
+ },
50
+ 'slide-down': {
51
+ from: { transform: 'translateY(-100%)' },
52
+ to: { transform: 'translateY(0)' },
53
+ },
54
+ 'slide-left': {
55
+ from: { transform: 'translateX(-100%)' },
56
+ to: { transform: 'translateX(0)' },
57
+ },
58
+ 'slide-right': {
59
+ from: { transform: 'translateX(100%)' },
60
+ to: { transform: 'translateX(0)' },
61
+ },
62
+ 'bounce': {
63
+ from: { opacity: 0, transform: 'translateY(-60px)' },
64
+ to: { opacity: 1, transform: 'translateY(0)' },
65
+ },
66
+ 'rotate-in': {
67
+ from: { opacity: 0, transform: 'rotate(-180deg) scale(0.5)' },
68
+ to: { opacity: 1, transform: 'rotate(0deg) scale(1)' },
69
+ },
70
+ 'blur-in': {
71
+ from: { opacity: 0, filter: 'blur(12px)' },
72
+ to: { opacity: 1, filter: 'blur(0px)' },
73
+ },
74
+ 'skew-in': {
75
+ from: { opacity: 0, transform: 'skewX(20deg) translateX(30px)' },
76
+ to: { opacity: 1, transform: 'skewX(0deg) translateX(0)' },
77
+ },
78
+ 'scale-x': {
79
+ from: { transform: 'scaleX(0)' },
80
+ to: { transform: 'scaleX(1)' },
81
+ },
82
+ 'scale-y': {
83
+ from: { transform: 'scaleY(0)' },
84
+ to: { transform: 'scaleY(1)' },
85
+ },
86
+ 'shimmer': {
87
+ from: { opacity: 0.5, filter: 'brightness(1)' },
88
+ to: { opacity: 1, filter: 'brightness(1.5)' },
89
+ },
90
+ 'pulse': {
91
+ from: { transform: 'scale(1)' },
92
+ to: { transform: 'scale(1.05)' },
93
+ },
94
+ 'swing': {
95
+ from: { transform: 'rotate(-10deg)' },
96
+ to: { transform: 'rotate(10deg)' },
97
+ },
98
+ };
99
+ function resolvePreset(animation) {
100
+ var _a;
101
+ if (typeof animation === 'string') {
102
+ return (_a = PRESETS[animation]) !== null && _a !== void 0 ? _a : PRESETS['fade-in-up'];
103
+ }
104
+ if (Array.isArray(animation)) {
105
+ const combined = { from: {}, to: {} };
106
+ animation.forEach(name => {
107
+ const preset = PRESETS[name];
108
+ if (preset) {
109
+ Object.entries(preset.from).forEach(([key, val]) => {
110
+ if (key === 'transform' && combined.from[key]) {
111
+ combined.from[key] = `${combined.from[key]} ${val}`;
112
+ }
113
+ else {
114
+ combined.from[key] = val;
115
+ }
116
+ });
117
+ Object.entries(preset.to).forEach(([key, val]) => {
118
+ if (key === 'transform' && combined.to[key]) {
119
+ combined.to[key] = `${combined.to[key]} ${val}`;
120
+ }
121
+ else {
122
+ combined.to[key] = val;
123
+ }
124
+ });
125
+ }
126
+ });
127
+ return combined;
128
+ }
129
+ return animation;
130
+ }
131
+ /** Easing to CSS cubic-bezier mapping */
132
+ const EASING_MAP = {
133
+ linear: 'linear',
134
+ ease: 'ease',
135
+ 'ease-in': 'ease-in',
136
+ 'ease-out': 'ease-out',
137
+ 'ease-in-out': 'ease-in-out',
138
+ spring: 'cubic-bezier(0.34, 1.56, 0.64, 1)',
139
+ };
140
+ function resolveEasing(easing) {
141
+ var _a;
142
+ return (_a = EASING_MAP[easing]) !== null && _a !== void 0 ? _a : easing;
143
+ }
144
+
145
+ /**
146
+ * use-scroll-animate - Core Implementation
147
+ * Uses IntersectionObserver + Web Animations API for zero-dependency,
148
+ * high-performance scroll-triggered animations.
149
+ */
150
+ const DEFAULT_CONFIG = {
151
+ defaultAnimation: 'fade-in-up',
152
+ defaultDuration: 600,
153
+ defaultDelay: 0,
154
+ defaultEasing: 'ease',
155
+ defaultThreshold: 0.1,
156
+ defaultRootMargin: '0px',
157
+ defaultRepeat: false,
158
+ defaultOnce: true,
159
+ defaultOffset: 0,
160
+ hiddenClass: 'sa-hidden',
161
+ visibleClass: 'sa-visible',
162
+ useClassNames: false,
163
+ disabled: false,
164
+ root: null,
165
+ };
166
+ const DEFAULT_OPTIONS = {
167
+ stagger: 0,
168
+ parallax: {},
169
+ onStart: () => undefined,
170
+ onComplete: () => undefined,
171
+ onEnter: () => undefined,
172
+ onLeave: () => undefined,
173
+ onProgress: () => undefined,
174
+ };
175
+ function parseDataAttributes(el, config) {
176
+ const dataset = el.dataset;
177
+ const opts = {};
178
+ if (dataset.saAnimation) {
179
+ const anim = dataset.saAnimation;
180
+ opts.animation = anim.includes(',')
181
+ ? anim.split(',').map(s => s.trim())
182
+ : anim;
183
+ }
184
+ if (dataset.saDuration)
185
+ opts.duration = parseInt(dataset.saDuration, 10);
186
+ if (dataset.saDelay)
187
+ opts.delay = parseInt(dataset.saDelay, 10);
188
+ if (dataset.saEasing)
189
+ opts.easing = dataset.saEasing;
190
+ if (dataset.saThreshold) {
191
+ const t = dataset.saThreshold;
192
+ opts.threshold = t.includes(',') ? t.split(',').map(parseFloat) : parseFloat(t);
193
+ }
194
+ if (dataset.saRootMargin)
195
+ opts.rootMargin = dataset.saRootMargin;
196
+ if (dataset.saRepeat !== undefined)
197
+ opts.repeat = dataset.saRepeat !== 'false';
198
+ if (dataset.saOnce !== undefined)
199
+ opts.once = dataset.saOnce !== 'false';
200
+ if (dataset.saOffset)
201
+ opts.offset = parseInt(dataset.saOffset, 10);
202
+ if (dataset.saStagger)
203
+ opts.stagger = parseInt(dataset.saStagger, 10);
204
+ if (dataset.saParallaxX || dataset.saParallaxY || dataset.saParallaxRotate || dataset.saParallaxScale) {
205
+ opts.parallax = {
206
+ x: dataset.saParallaxX,
207
+ y: dataset.saParallaxY,
208
+ rotate: dataset.saParallaxRotate ? parseFloat(dataset.saParallaxRotate) : undefined,
209
+ scale: dataset.saParallaxScale ? parseFloat(dataset.saParallaxScale) : undefined,
210
+ speed: dataset.saParallaxSpeed ? parseFloat(dataset.saParallaxSpeed) : 1,
211
+ };
212
+ }
213
+ return mergeOptions(opts, config);
214
+ }
215
+ function mergeOptions(opts, config) {
216
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
217
+ const repeat = (_a = opts.repeat) !== null && _a !== void 0 ? _a : config.defaultRepeat;
218
+ return {
219
+ animation: (_b = opts.animation) !== null && _b !== void 0 ? _b : config.defaultAnimation,
220
+ duration: (_c = opts.duration) !== null && _c !== void 0 ? _c : config.defaultDuration,
221
+ delay: (_d = opts.delay) !== null && _d !== void 0 ? _d : config.defaultDelay,
222
+ easing: (_e = opts.easing) !== null && _e !== void 0 ? _e : config.defaultEasing,
223
+ threshold: (_f = opts.threshold) !== null && _f !== void 0 ? _f : config.defaultThreshold,
224
+ rootMargin: (_g = opts.rootMargin) !== null && _g !== void 0 ? _g : config.defaultRootMargin,
225
+ repeat: repeat,
226
+ once: (_h = opts.once) !== null && _h !== void 0 ? _h : (repeat ? false : config.defaultOnce),
227
+ offset: (_j = opts.offset) !== null && _j !== void 0 ? _j : config.defaultOffset,
228
+ stagger: (_k = opts.stagger) !== null && _k !== void 0 ? _k : DEFAULT_OPTIONS.stagger,
229
+ parallax: (_l = opts.parallax) !== null && _l !== void 0 ? _l : DEFAULT_OPTIONS.parallax,
230
+ onStart: (_m = opts.onStart) !== null && _m !== void 0 ? _m : DEFAULT_OPTIONS.onStart,
231
+ onComplete: (_o = opts.onComplete) !== null && _o !== void 0 ? _o : DEFAULT_OPTIONS.onComplete,
232
+ onEnter: (_p = opts.onEnter) !== null && _p !== void 0 ? _p : DEFAULT_OPTIONS.onEnter,
233
+ onLeave: (_q = opts.onLeave) !== null && _q !== void 0 ? _q : DEFAULT_OPTIONS.onLeave,
234
+ onProgress: (_r = opts.onProgress) !== null && _r !== void 0 ? _r : DEFAULT_OPTIONS.onProgress,
235
+ };
236
+ }
237
+ function resolveTargets(target) {
238
+ if (typeof target === 'string') {
239
+ return Array.from(document.querySelectorAll(target));
240
+ }
241
+ if (target instanceof Element) {
242
+ return [target];
243
+ }
244
+ if (target instanceof NodeList) {
245
+ return Array.from(target);
246
+ }
247
+ if (Array.isArray(target)) {
248
+ return target;
249
+ }
250
+ return [];
251
+ }
252
+ function prefersReducedMotion() {
253
+ var _a, _b;
254
+ return (_b = (_a = window.matchMedia) === null || _a === void 0 ? void 0 : _a.call(window, '(prefers-reduced-motion: reduce)').matches) !== null && _b !== void 0 ? _b : false;
255
+ }
256
+ function runAnimation(el, opts, config, staggerIndex = 0) {
257
+ const { animation, duration, delay, easing, stagger, onStart, onComplete } = opts;
258
+ const totalDelay = delay + staggerIndex * stagger;
259
+ const preset = resolvePreset(animation);
260
+ const easingValue = resolveEasing(easing);
261
+ if (config.useClassNames) {
262
+ if (totalDelay > 0) {
263
+ el.style.animationDelay = `${totalDelay}ms`;
264
+ }
265
+ el.classList.remove(config.hiddenClass);
266
+ el.classList.add(config.visibleClass);
267
+ onStart(el);
268
+ setTimeout(() => onComplete(el), duration + totalDelay);
269
+ return;
270
+ }
271
+ const keyframes = [
272
+ preset.from,
273
+ preset.to,
274
+ ];
275
+ const timing = {
276
+ duration,
277
+ delay: totalDelay,
278
+ easing: easingValue,
279
+ fill: 'both',
280
+ };
281
+ // Check if Web Animations API is supported
282
+ if (typeof el.animate === 'function') {
283
+ const anim = el.animate(keyframes, timing);
284
+ onStart(el);
285
+ anim.onfinish = () => onComplete(el);
286
+ }
287
+ else {
288
+ // Fallback for older browsers
289
+ el.style.opacity = '1';
290
+ el.style.transform = 'none';
291
+ onStart(el);
292
+ onComplete(el);
293
+ }
294
+ }
295
+ function applyParallax(el, progress, parallax) {
296
+ const { x = 0, y = 0, rotate = 0, scale = 1, speed = 1 } = parallax;
297
+ const p = (progress - 0.5) * 2 * speed;
298
+ let transform = '';
299
+ if (x)
300
+ transform += ` translateX(${typeof x === 'number' ? x * p + 'px' : 'calc(' + x + ' * ' + p + ')'})`;
301
+ if (y)
302
+ transform += ` translateY(${typeof y === 'number' ? y * p + 'px' : 'calc(' + y + ' * ' + p + ')'})`;
303
+ if (rotate)
304
+ transform += ` rotate(${rotate * p}deg)`;
305
+ if (scale !== 1)
306
+ transform += ` scale(${1 + (scale - 1) * p})`;
307
+ el.style.transform = transform;
308
+ }
309
+ function hideElement(el, config) {
310
+ if (config.useClassNames) {
311
+ el.classList.add(config.hiddenClass);
312
+ el.classList.remove(config.visibleClass);
313
+ }
314
+ else {
315
+ el.style.opacity = '0';
316
+ }
317
+ }
318
+ function createScrollAnimate(userConfig = {}) {
319
+ let config = { ...DEFAULT_CONFIG, ...userConfig };
320
+ const registry = new Map();
321
+ function createObserver(opts) {
322
+ // Handle offset by adjusting rootMargin
323
+ const rootMargin = opts.offset !== 0
324
+ ? `${opts.rootMargin.split(' ')[0]} 0px -${opts.offset}px 0px`
325
+ : opts.rootMargin;
326
+ return new IntersectionObserver((entries) => {
327
+ entries.forEach((entry) => {
328
+ var _a;
329
+ const record = registry.get(entry.target);
330
+ if (!record)
331
+ return;
332
+ if (entry.isIntersecting) {
333
+ opts.onEnter(entry.target);
334
+ if (!record.animated || opts.repeat) {
335
+ const parent = entry.target.parentElement;
336
+ let staggerIndex = 0;
337
+ if (parent && opts.stagger > 0) {
338
+ // BUG FIX: Use a more robust way to find index among siblings that are also observed
339
+ const siblings = Array.from(parent.children).filter((c) => registry.has(c));
340
+ staggerIndex = siblings.indexOf(entry.target);
341
+ }
342
+ if (!config.disabled && !prefersReducedMotion()) {
343
+ runAnimation(entry.target, opts, config, staggerIndex);
344
+ }
345
+ else {
346
+ entry.target.style.opacity = '1';
347
+ entry.target.style.transform = '';
348
+ }
349
+ record.animated = true;
350
+ // Handle 'once' functionality
351
+ if (opts.once) {
352
+ record.observer.unobserve(entry.target);
353
+ (_a = record.progressObserver) === null || _a === void 0 ? void 0 : _a.unobserve(entry.target);
354
+ // We don't delete from registry yet to allow other potential interactions
355
+ }
356
+ }
357
+ }
358
+ else {
359
+ opts.onLeave(entry.target);
360
+ if (opts.repeat && record.animated) {
361
+ hideElement(entry.target, config);
362
+ record.animated = false;
363
+ }
364
+ }
365
+ });
366
+ }, {
367
+ threshold: typeof opts.threshold === 'number' ? opts.threshold : opts.threshold[0],
368
+ rootMargin: rootMargin,
369
+ root: config.root,
370
+ });
371
+ }
372
+ function createProgressObserver(el, opts) {
373
+ const thresholds = [];
374
+ for (let i = 0; i <= 100; i++)
375
+ thresholds.push(i / 100);
376
+ return new IntersectionObserver((entries) => {
377
+ entries.forEach((entry) => {
378
+ const progress = entry.intersectionRatio;
379
+ opts.onProgress(el, progress);
380
+ if (Object.keys(opts.parallax).length > 0) {
381
+ applyParallax(el, progress, opts.parallax);
382
+ }
383
+ });
384
+ }, {
385
+ threshold: thresholds,
386
+ rootMargin: opts.rootMargin,
387
+ root: config.root,
388
+ });
389
+ }
390
+ function observeElement(el, opts) {
391
+ if (registry.has(el))
392
+ return;
393
+ if (!config.useClassNames) {
394
+ hideElement(el, config);
395
+ }
396
+ const observer = createObserver(opts);
397
+ observer.observe(el);
398
+ let progressObserver;
399
+ if (Object.keys(opts.parallax).length > 0 || opts.onProgress !== DEFAULT_OPTIONS.onProgress) {
400
+ progressObserver = createProgressObserver(el, opts);
401
+ progressObserver.observe(el);
402
+ }
403
+ registry.set(el, {
404
+ element: el,
405
+ options: opts,
406
+ observer,
407
+ animated: false,
408
+ progressObserver
409
+ });
410
+ }
411
+ const instance = {
412
+ observe(target, options = {}) {
413
+ const elements = resolveTargets(target);
414
+ const opts = mergeOptions(options, config);
415
+ elements.forEach((el) => observeElement(el, opts));
416
+ },
417
+ unobserve(target) {
418
+ const elements = resolveTargets(target);
419
+ elements.forEach((el) => {
420
+ var _a;
421
+ const record = registry.get(el);
422
+ if (record) {
423
+ record.observer.disconnect(); // BUG FIX: Use disconnect for better cleanup
424
+ (_a = record.progressObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
425
+ registry.delete(el);
426
+ }
427
+ });
428
+ },
429
+ init(rootElement = document) {
430
+ const elements = rootElement.querySelectorAll('[data-sa]');
431
+ elements.forEach((el) => {
432
+ const opts = parseDataAttributes(el, config);
433
+ observeElement(el, opts);
434
+ });
435
+ },
436
+ destroy() {
437
+ registry.forEach((record) => {
438
+ var _a;
439
+ record.observer.disconnect();
440
+ (_a = record.progressObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
441
+ });
442
+ registry.clear();
443
+ },
444
+ refresh() {
445
+ const entries = Array.from(registry.entries());
446
+ instance.destroy();
447
+ entries.forEach(([el, record]) => {
448
+ observeElement(el, record.options);
449
+ });
450
+ },
451
+ animate(target, options = {}) {
452
+ const elements = resolveTargets(target);
453
+ const opts = mergeOptions(options, config);
454
+ elements.forEach((el) => {
455
+ runAnimation(el, opts, config, 0);
456
+ });
457
+ },
458
+ getObservedElements() {
459
+ return Array.from(registry.values());
460
+ },
461
+ configure(newConfig) {
462
+ config = { ...config, ...newConfig };
463
+ },
464
+ };
465
+ return instance;
466
+ }
467
+
468
+ /**
469
+ * use-scroll-animate - React Integration
470
+ * Provides useScrollAnimate and useScrollRef hooks for React applications.
471
+ */
472
+ function createReactHooks(React) {
473
+ function useScrollAnimate(options = {}) {
474
+ const ref = React.useRef(null);
475
+ React.useEffect(() => {
476
+ const el = ref.current;
477
+ if (!el)
478
+ return;
479
+ const { animation = 'fade-in-up', duration = 600, delay = 0, easing = 'ease', threshold = 0.1, rootMargin = '0px', repeat = false, parallax = {}, onStart, onComplete, onEnter, onLeave, onProgress, } = options;
480
+ const preset = resolvePreset(animation);
481
+ const easingValue = resolveEasing(easing);
482
+ let animated = false;
483
+ el.style.opacity = '0';
484
+ const observer = new IntersectionObserver((entries) => {
485
+ entries.forEach((entry) => {
486
+ if (entry.isIntersecting) {
487
+ onEnter === null || onEnter === void 0 ? void 0 : onEnter(el);
488
+ if (!animated || repeat) {
489
+ const keyframes = [
490
+ preset.from,
491
+ preset.to,
492
+ ];
493
+ const anim = el.animate(keyframes, {
494
+ duration,
495
+ delay,
496
+ easing: easingValue,
497
+ fill: 'both',
498
+ });
499
+ onStart === null || onStart === void 0 ? void 0 : onStart(el);
500
+ anim.onfinish = () => onComplete === null || onComplete === void 0 ? void 0 : onComplete(el);
501
+ animated = true;
502
+ if (!repeat && !Object.keys(parallax).length && !onProgress) {
503
+ observer.unobserve(el);
504
+ }
505
+ }
506
+ }
507
+ else {
508
+ onLeave === null || onLeave === void 0 ? void 0 : onLeave(el);
509
+ if (repeat && animated) {
510
+ el.style.opacity = '0';
511
+ animated = false;
512
+ }
513
+ }
514
+ });
515
+ }, { threshold: typeof threshold === 'number' ? threshold : threshold[0], rootMargin });
516
+ let progressObserver = null;
517
+ if (Object.keys(parallax).length > 0 || onProgress) {
518
+ const thresholds = [];
519
+ for (let i = 0; i <= 100; i++)
520
+ thresholds.push(i / 100);
521
+ progressObserver = new IntersectionObserver((entries) => {
522
+ entries.forEach((entry) => {
523
+ const progress = entry.intersectionRatio;
524
+ onProgress === null || onProgress === void 0 ? void 0 : onProgress(el, progress);
525
+ if (Object.keys(parallax).length > 0) {
526
+ const { x = 0, y = 0, rotate = 0, scale = 1, speed = 1 } = parallax;
527
+ const p = (progress - 0.5) * 2 * speed;
528
+ let transform = '';
529
+ if (x)
530
+ transform += ` translateX(${typeof x === 'number' ? x * p + 'px' : 'calc(' + x + ' * ' + p + ')'})`;
531
+ if (y)
532
+ transform += ` translateY(${typeof y === 'number' ? y * p + 'px' : 'calc(' + y + ' * ' + p + ')'})`;
533
+ if (rotate)
534
+ transform += ` rotate(${rotate * p}deg)`;
535
+ if (scale !== 1)
536
+ transform += ` scale(${1 + (scale - 1) * p})`;
537
+ el.style.transform = transform;
538
+ }
539
+ });
540
+ }, { threshold: thresholds, rootMargin });
541
+ progressObserver.observe(el);
542
+ }
543
+ observer.observe(el);
544
+ return () => {
545
+ observer.disconnect();
546
+ progressObserver === null || progressObserver === void 0 ? void 0 : progressObserver.disconnect();
547
+ };
548
+ }, []);
549
+ return ref;
550
+ }
551
+ function useScrollStagger(options = {}) {
552
+ const ref = React.useRef(null);
553
+ React.useEffect(() => {
554
+ const container = ref.current;
555
+ if (!container)
556
+ return;
557
+ const { animation = 'fade-in-up', duration = 600, delay = 0, easing = 'ease', threshold = 0.1, rootMargin = '0px', stagger = 80, } = options;
558
+ const children = Array.from(container.children);
559
+ const preset = resolvePreset(animation);
560
+ const easingValue = resolveEasing(easing);
561
+ children.forEach((child) => {
562
+ child.style.opacity = '0';
563
+ });
564
+ let animated = false;
565
+ const observer = new IntersectionObserver((entries) => {
566
+ entries.forEach((entry) => {
567
+ if (entry.isIntersecting && !animated) {
568
+ animated = true;
569
+ children.forEach((child, i) => {
570
+ child.animate([preset.from, preset.to], {
571
+ duration,
572
+ delay: delay + i * stagger,
573
+ easing: easingValue,
574
+ fill: 'both',
575
+ });
576
+ });
577
+ observer.unobserve(container);
578
+ }
579
+ });
580
+ }, { threshold: typeof threshold === 'number' ? threshold : threshold[0], rootMargin });
581
+ observer.observe(container);
582
+ return () => observer.disconnect();
583
+ }, []);
584
+ return ref;
585
+ }
586
+ return { useScrollAnimate, useScrollStagger };
587
+ }
588
+
589
+ /**
590
+ * use-scroll-animate - Vue 3 Integration
591
+ * Provides useScrollAnimate composable for Vue 3 applications.
592
+ */
593
+ function createVueComposables(Vue) {
594
+ function useScrollAnimate(options = {}) {
595
+ const animateRef = Vue.ref(null);
596
+ let observer = null;
597
+ let progressObserver = null;
598
+ Vue.onMounted(() => {
599
+ const el = animateRef.value;
600
+ if (!el)
601
+ return;
602
+ const { animation = 'fade-in-up', duration = 600, delay = 0, easing = 'ease', threshold = 0.1, rootMargin = '0px', repeat = false, parallax = {}, onStart, onComplete, onEnter, onLeave, onProgress, } = options;
603
+ const preset = resolvePreset(animation);
604
+ const easingValue = resolveEasing(easing);
605
+ let animated = false;
606
+ el.style.opacity = '0';
607
+ observer = new IntersectionObserver((entries) => {
608
+ entries.forEach((entry) => {
609
+ if (entry.isIntersecting) {
610
+ onEnter === null || onEnter === void 0 ? void 0 : onEnter(el);
611
+ if (!animated || repeat) {
612
+ const anim = el.animate([preset.from, preset.to], {
613
+ duration,
614
+ delay,
615
+ easing: easingValue,
616
+ fill: 'both',
617
+ });
618
+ onStart === null || onStart === void 0 ? void 0 : onStart(el);
619
+ anim.onfinish = () => onComplete === null || onComplete === void 0 ? void 0 : onComplete(el);
620
+ animated = true;
621
+ if (!repeat && !Object.keys(parallax).length && !onProgress) {
622
+ observer === null || observer === void 0 ? void 0 : observer.unobserve(el);
623
+ }
624
+ }
625
+ }
626
+ else {
627
+ onLeave === null || onLeave === void 0 ? void 0 : onLeave(el);
628
+ if (repeat && animated) {
629
+ el.style.opacity = '0';
630
+ animated = false;
631
+ }
632
+ }
633
+ });
634
+ }, { threshold: typeof threshold === 'number' ? threshold : threshold[0], rootMargin });
635
+ if (Object.keys(parallax).length > 0 || onProgress) {
636
+ const thresholds = [];
637
+ for (let i = 0; i <= 100; i++)
638
+ thresholds.push(i / 100);
639
+ progressObserver = new IntersectionObserver((entries) => {
640
+ entries.forEach((entry) => {
641
+ const progress = entry.intersectionRatio;
642
+ onProgress === null || onProgress === void 0 ? void 0 : onProgress(el, progress);
643
+ if (Object.keys(parallax).length > 0) {
644
+ const { x = 0, y = 0, rotate = 0, scale = 1, speed = 1 } = parallax;
645
+ const p = (progress - 0.5) * 2 * speed;
646
+ let transform = '';
647
+ if (x)
648
+ transform += ` translateX(${typeof x === 'number' ? x * p + 'px' : 'calc(' + x + ' * ' + p + ')'})`;
649
+ if (y)
650
+ transform += ` translateY(${typeof y === 'number' ? y * p + 'px' : 'calc(' + y + ' * ' + p + ')'})`;
651
+ if (rotate)
652
+ transform += ` rotate(${rotate * p}deg)`;
653
+ if (scale !== 1)
654
+ transform += ` scale(${1 + (scale - 1) * p})`;
655
+ el.style.transform = transform;
656
+ }
657
+ });
658
+ }, { threshold: thresholds, rootMargin });
659
+ progressObserver.observe(el);
660
+ }
661
+ observer.observe(el);
662
+ });
663
+ Vue.onUnmounted(() => {
664
+ observer === null || observer === void 0 ? void 0 : observer.disconnect();
665
+ progressObserver === null || progressObserver === void 0 ? void 0 : progressObserver.disconnect();
666
+ });
667
+ return { animateRef };
668
+ }
669
+ return { useScrollAnimate };
670
+ }
671
+
672
+ /**
673
+ * use-scroll-animate
674
+ *
675
+ * A lightweight (~4KB gzipped), dependency-free scroll animation library
676
+ * for modern web applications. Built with TypeScript, powered by
677
+ * IntersectionObserver and the Web Animations API.
678
+ *
679
+ * @version 1.0.0
680
+ * @license MIT
681
+ * @see https://github.com/use-scroll-animate/use-scroll-animate
682
+ */
683
+ /**
684
+ * Default singleton instance of ScrollAnimate.
685
+ * Ready to use out of the box with sensible defaults.
686
+ *
687
+ * @example
688
+ * ```js
689
+ * import ScrollAnimate from 'use-scroll-animate';
690
+ *
691
+ * // Auto-initialize all elements with data-sa attribute
692
+ * ScrollAnimate.init();
693
+ *
694
+ * // Or manually observe elements
695
+ * ScrollAnimate.observe('.my-element', { animation: 'fade-in-up' });
696
+ * ```
697
+ */
698
+ const ScrollAnimate = createScrollAnimate();
699
+
700
+ exports.EASING_MAP = EASING_MAP;
701
+ exports.PRESETS = PRESETS;
702
+ exports.createReactHooks = createReactHooks;
703
+ exports.createScrollAnimate = createScrollAnimate;
704
+ exports.createVueComposables = createVueComposables;
705
+ exports.default = ScrollAnimate;
706
+ exports.resolveEasing = resolveEasing;
707
+ exports.resolvePreset = resolvePreset;
708
+ //# sourceMappingURL=index.js.map