use-scroll-animate 1.5.0 → 2.0.1
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/CHANGELOG.md +67 -0
- package/README.md +145 -10
- package/README_ja.md +57 -1
- package/README_zh.md +68 -1
- package/dist/{index.esm.js → chunks/core-BP-a1iNc.js} +297 -404
- package/dist/chunks/core-BP-a1iNc.js.map +1 -0
- package/dist/{index.mjs → chunks/core-B_J4rXbC.cjs} +312 -404
- package/dist/chunks/core-B_J4rXbC.cjs.map +1 -0
- package/dist/chunks/stagger-DHw0ExuR.cjs +86 -0
- package/dist/chunks/stagger-DHw0ExuR.cjs.map +1 -0
- package/dist/chunks/stagger-DTv_WiUQ.js +84 -0
- package/dist/chunks/stagger-DTv_WiUQ.js.map +1 -0
- package/dist/element.cjs +97 -0
- package/dist/element.cjs.map +1 -0
- package/dist/{types/types.d.ts → element.d.cts} +73 -10
- package/dist/element.d.ts +216 -0
- package/dist/element.js +95 -0
- package/dist/element.js.map +1 -0
- package/dist/element.umd.js +2 -0
- package/dist/element.umd.js.map +1 -0
- package/dist/index.cjs +241 -0
- package/dist/index.cjs.map +1 -0
- package/dist/{index.d.mts → index.d.cts} +84 -51
- package/dist/index.d.ts +84 -51
- package/dist/index.js +98 -1133
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +5 -3
- package/dist/index.umd.js.map +1 -1
- package/dist/react.cjs +67 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +159 -0
- package/dist/react.d.ts +159 -0
- package/dist/react.js +64 -0
- package/dist/react.js.map +1 -0
- package/dist/solid.cjs +62 -0
- package/dist/solid.cjs.map +1 -0
- package/dist/solid.d.cts +245 -0
- package/dist/solid.d.ts +245 -0
- package/dist/solid.js +58 -0
- package/dist/solid.js.map +1 -0
- package/dist/svelte.cjs +65 -0
- package/dist/svelte.cjs.map +1 -0
- package/dist/svelte.d.cts +244 -0
- package/dist/svelte.d.ts +244 -0
- package/dist/svelte.js +62 -0
- package/dist/svelte.js.map +1 -0
- package/dist/vue.cjs +61 -0
- package/dist/vue.cjs.map +1 -0
- package/dist/vue.d.cts +159 -0
- package/dist/vue.d.ts +159 -0
- package/dist/vue.js +59 -0
- package/dist/vue.js.map +1 -0
- package/docs/API.md +139 -0
- package/docs/deprecations.md +20 -0
- package/docs/migration-from-aos.md +67 -0
- package/docs/migration-from-gsap-scrolltrigger.md +80 -0
- package/package.json +86 -15
- package/dist/index.esm.js.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/types/core.d.ts +0 -41
- package/dist/types/index.d.ts +0 -36
- package/dist/types/presets.d.ts +0 -15
- package/dist/types/react.d.ts +0 -29
- package/dist/types/sequence.d.ts +0 -38
- package/dist/types/stagger.d.ts +0 -25
- package/dist/types/vue.d.ts +0 -28
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* use-scroll-animate - Animation Presets
|
|
3
5
|
* Defines keyframes for all built-in animation presets
|
|
@@ -138,9 +140,8 @@ const PRESETS = {
|
|
|
138
140
|
},
|
|
139
141
|
};
|
|
140
142
|
function resolvePreset(animation) {
|
|
141
|
-
var _a;
|
|
142
143
|
if (typeof animation === 'string') {
|
|
143
|
-
return
|
|
144
|
+
return PRESETS[animation.trim()] ?? PRESETS['fade-in-up'];
|
|
144
145
|
}
|
|
145
146
|
if (Array.isArray(animation)) {
|
|
146
147
|
const combined = { from: {}, to: {} };
|
|
@@ -181,9 +182,8 @@ const EASING_MAP = {
|
|
|
181
182
|
'heavy-bounce': 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
|
|
182
183
|
};
|
|
183
184
|
function resolveEasing(easing) {
|
|
184
|
-
var _a;
|
|
185
185
|
if (typeof easing === 'string') {
|
|
186
|
-
return
|
|
186
|
+
return EASING_MAP[easing] ?? easing;
|
|
187
187
|
}
|
|
188
188
|
if (Array.isArray(easing)) {
|
|
189
189
|
return `cubic-bezier(${easing.join(', ')})`;
|
|
@@ -217,6 +217,7 @@ const DEFAULT_CONFIG = {
|
|
|
217
217
|
disabled: false,
|
|
218
218
|
root: null,
|
|
219
219
|
autoUnregister: true,
|
|
220
|
+
defaultEngine: 'auto',
|
|
220
221
|
};
|
|
221
222
|
const noop = () => undefined;
|
|
222
223
|
/* ------------------------------------------------------------------ */
|
|
@@ -236,6 +237,23 @@ function prefersReducedMotion() {
|
|
|
236
237
|
}
|
|
237
238
|
return !!reducedMotionQuery && reducedMotionQuery.matches;
|
|
238
239
|
}
|
|
240
|
+
let scrollTimelineSupported;
|
|
241
|
+
/**
|
|
242
|
+
* Whether the browser can run presets on a native scroll-driven timeline:
|
|
243
|
+
* `CSS.supports('animation-timeline: view()')` plus the `ViewTimeline`
|
|
244
|
+
* constructor used to attach it from JavaScript. Cached. SSR-safe.
|
|
245
|
+
*/
|
|
246
|
+
function supportsScrollTimeline() {
|
|
247
|
+
if (scrollTimelineSupported === undefined) {
|
|
248
|
+
scrollTimelineSupported =
|
|
249
|
+
hasDOM() &&
|
|
250
|
+
typeof CSS !== 'undefined' &&
|
|
251
|
+
typeof CSS.supports === 'function' &&
|
|
252
|
+
CSS.supports('animation-timeline: view()') &&
|
|
253
|
+
typeof globalThis.ViewTimeline === 'function';
|
|
254
|
+
}
|
|
255
|
+
return scrollTimelineSupported;
|
|
256
|
+
}
|
|
239
257
|
/* ------------------------------------------------------------------ */
|
|
240
258
|
/* Option parsing */
|
|
241
259
|
/* ------------------------------------------------------------------ */
|
|
@@ -253,6 +271,7 @@ function lengthValue(value) {
|
|
|
253
271
|
return /^-?(\d+\.?\d*|\.\d+)$/.test(v) ? parseFloat(v) : v;
|
|
254
272
|
}
|
|
255
273
|
const DEFAULT_PROGRESS_VAR = '--sa-progress';
|
|
274
|
+
const DEFAULT_VIEW_RANGE = ['entry 0%', 'entry 100%'];
|
|
256
275
|
/** `''` (bare attribute) -> default name; `sa-progress` -> `--sa-progress`. */
|
|
257
276
|
function normalizeVar(name) {
|
|
258
277
|
const v = name.trim();
|
|
@@ -260,23 +279,28 @@ function normalizeVar(name) {
|
|
|
260
279
|
return DEFAULT_PROGRESS_VAR;
|
|
261
280
|
return v.startsWith('--') ? v : `--${v}`;
|
|
262
281
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
282
|
+
/**
|
|
283
|
+
* @internal Read options from string attributes. `get(name)` returns the raw
|
|
284
|
+
* value of the attribute `name` (kebab-case, e.g. `root-margin`), `''` for a
|
|
285
|
+
* bare attribute and `undefined` when absent. Shared by `data-sa-*` and the
|
|
286
|
+
* `<scroll-animate>` element.
|
|
287
|
+
*/
|
|
288
|
+
function readOptions(get) {
|
|
266
289
|
const opts = {};
|
|
267
|
-
|
|
268
|
-
|
|
290
|
+
const anim = get('animation');
|
|
291
|
+
if (anim) {
|
|
269
292
|
opts.animation = (anim.includes(',') ? anim.split(',').map((s) => s.trim()) : anim.trim());
|
|
270
293
|
}
|
|
271
|
-
opts.duration = num(
|
|
272
|
-
opts.delay = num(
|
|
273
|
-
|
|
274
|
-
|
|
294
|
+
opts.duration = num(get('duration'));
|
|
295
|
+
opts.delay = num(get('delay'));
|
|
296
|
+
const easing = get('easing');
|
|
297
|
+
if (easing) {
|
|
298
|
+
const e = easing.trim();
|
|
275
299
|
if (e.startsWith('[')) {
|
|
276
300
|
try {
|
|
277
301
|
opts.easing = JSON.parse(e);
|
|
278
302
|
}
|
|
279
|
-
catch
|
|
303
|
+
catch {
|
|
280
304
|
// Malformed JSON: fall back to the default easing instead of throwing
|
|
281
305
|
}
|
|
282
306
|
}
|
|
@@ -284,58 +308,95 @@ function parseDataAttributes(el, config) {
|
|
|
284
308
|
opts.easing = e;
|
|
285
309
|
}
|
|
286
310
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
opts.threshold = t.includes(',')
|
|
290
|
-
? t.split(',').map(parseFloat).filter(Number.isFinite)
|
|
291
|
-
: num(t);
|
|
311
|
+
const t = get('threshold');
|
|
312
|
+
if (t) {
|
|
313
|
+
opts.threshold = t.includes(',') ? t.split(',').map(parseFloat).filter(Number.isFinite) : num(t);
|
|
292
314
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if (
|
|
298
|
-
opts.
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
if (
|
|
315
|
+
const rootMargin = get('root-margin');
|
|
316
|
+
if (rootMargin)
|
|
317
|
+
opts.rootMargin = rootMargin;
|
|
318
|
+
const repeat = get('repeat');
|
|
319
|
+
if (repeat !== undefined)
|
|
320
|
+
opts.repeat = repeat !== 'false';
|
|
321
|
+
const once = get('once');
|
|
322
|
+
if (once !== undefined)
|
|
323
|
+
opts.once = once !== 'false';
|
|
324
|
+
opts.offset = num(get('offset'));
|
|
325
|
+
opts.stagger = num(get('stagger'));
|
|
326
|
+
const progressVar = get('progress-var');
|
|
327
|
+
if (progressVar !== undefined)
|
|
328
|
+
opts.progressVar = normalizeVar(progressVar);
|
|
329
|
+
const engine = get('engine')?.trim();
|
|
330
|
+
if (engine === 'auto' || engine === 'js' || engine === 'css')
|
|
331
|
+
opts.engine = engine;
|
|
332
|
+
const range = get('view-range');
|
|
333
|
+
if (range) {
|
|
334
|
+
const [start, end] = range.split(',').map((s) => s.trim());
|
|
335
|
+
if (start && end)
|
|
336
|
+
opts.viewRange = [start, end];
|
|
337
|
+
}
|
|
338
|
+
const exit = get('exit');
|
|
339
|
+
if (exit !== undefined) {
|
|
340
|
+
const e = exit.trim();
|
|
341
|
+
opts.exit = e === '' || e === 'true' ? true : e === 'false' ? false : (e.includes(',') ? e.split(',').map((s) => s.trim()) : e);
|
|
342
|
+
}
|
|
343
|
+
const progress = get('progress');
|
|
344
|
+
if (progress)
|
|
345
|
+
opts.progressMode = progress.trim() === 'scroll' ? 'scroll' : 'ratio';
|
|
346
|
+
const px = get('parallax-x');
|
|
347
|
+
const py = get('parallax-y');
|
|
348
|
+
const pr = get('parallax-rotate');
|
|
349
|
+
const ps = get('parallax-scale');
|
|
350
|
+
if (px || py || pr || ps) {
|
|
306
351
|
opts.parallax = {
|
|
307
|
-
x: lengthValue(
|
|
308
|
-
y: lengthValue(
|
|
309
|
-
rotate: num(
|
|
310
|
-
scale: num(
|
|
311
|
-
speed:
|
|
352
|
+
x: lengthValue(px),
|
|
353
|
+
y: lengthValue(py),
|
|
354
|
+
rotate: num(pr),
|
|
355
|
+
scale: num(ps),
|
|
356
|
+
speed: num(get('parallax-speed')) ?? 1,
|
|
312
357
|
};
|
|
313
358
|
}
|
|
314
|
-
|
|
359
|
+
// Drop keys that were not set, so they don't override defaults when spread.
|
|
360
|
+
Object.keys(opts).forEach((k) => opts[k] === undefined && delete opts[k]);
|
|
361
|
+
return opts;
|
|
362
|
+
}
|
|
363
|
+
function parseDataAttributes(el, config) {
|
|
364
|
+
return mergeOptions(readOptions((name) => {
|
|
365
|
+
const v = el.getAttribute(`data-sa-${name}`);
|
|
366
|
+
return v === null ? undefined : v;
|
|
367
|
+
}), config);
|
|
315
368
|
}
|
|
316
369
|
function mergeOptions(opts, config) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
370
|
+
let engine = opts.engine ?? config.defaultEngine;
|
|
371
|
+
// 'auto' keeps time-based semantics when the element asks for them explicitly.
|
|
372
|
+
if (engine === 'auto' && (opts.duration !== undefined || opts.delay !== undefined || opts.offset !== undefined || (opts.stagger ?? 0) > 0)) {
|
|
373
|
+
engine = 'js';
|
|
374
|
+
}
|
|
375
|
+
const exit = opts.exit ?? false;
|
|
376
|
+
const repeat = opts.repeat ?? (exit ? true : config.defaultRepeat);
|
|
377
|
+
const threshold = opts.threshold ?? config.defaultThreshold;
|
|
320
378
|
return {
|
|
321
|
-
animation:
|
|
322
|
-
duration:
|
|
323
|
-
delay:
|
|
324
|
-
easing:
|
|
379
|
+
animation: opts.animation ?? config.defaultAnimation,
|
|
380
|
+
duration: opts.duration ?? config.defaultDuration,
|
|
381
|
+
delay: opts.delay ?? config.defaultDelay,
|
|
382
|
+
easing: opts.easing ?? config.defaultEasing,
|
|
325
383
|
threshold: Array.isArray(threshold) && threshold.length === 0 ? config.defaultThreshold : threshold,
|
|
326
|
-
rootMargin:
|
|
384
|
+
rootMargin: opts.rootMargin ?? config.defaultRootMargin,
|
|
327
385
|
repeat,
|
|
328
|
-
once:
|
|
329
|
-
offset:
|
|
330
|
-
stagger:
|
|
331
|
-
parallax:
|
|
332
|
-
onStart:
|
|
333
|
-
onComplete:
|
|
334
|
-
onEnter:
|
|
335
|
-
onLeave:
|
|
336
|
-
onProgress:
|
|
337
|
-
progressMode:
|
|
386
|
+
once: opts.once ?? (repeat ? false : config.defaultOnce),
|
|
387
|
+
offset: opts.offset ?? config.defaultOffset,
|
|
388
|
+
stagger: opts.stagger ?? 0,
|
|
389
|
+
parallax: opts.parallax ?? {},
|
|
390
|
+
onStart: opts.onStart ?? noop,
|
|
391
|
+
onComplete: opts.onComplete ?? noop,
|
|
392
|
+
onEnter: opts.onEnter ?? noop,
|
|
393
|
+
onLeave: opts.onLeave ?? noop,
|
|
394
|
+
onProgress: opts.onProgress ?? noop,
|
|
395
|
+
progressMode: opts.progressMode ?? 'ratio',
|
|
338
396
|
progressVar: opts.progressVar ? normalizeVar(opts.progressVar) : '',
|
|
397
|
+
engine,
|
|
398
|
+
viewRange: opts.viewRange ?? DEFAULT_VIEW_RANGE,
|
|
399
|
+
exit,
|
|
339
400
|
};
|
|
340
401
|
}
|
|
341
402
|
function resolveTargets(target) {
|
|
@@ -356,14 +417,13 @@ function resolveTargets(target) {
|
|
|
356
417
|
* @internal
|
|
357
418
|
*/
|
|
358
419
|
function applyOffset(rootMargin, offset) {
|
|
359
|
-
var _a, _b, _c;
|
|
360
420
|
if (!offset)
|
|
361
421
|
return rootMargin;
|
|
362
422
|
const parts = (rootMargin || '0px').trim().split(/\s+/);
|
|
363
423
|
const top = parts[0];
|
|
364
|
-
const right =
|
|
365
|
-
const bottom =
|
|
366
|
-
const left =
|
|
424
|
+
const right = parts[1] ?? top;
|
|
425
|
+
const bottom = parts[2] ?? top;
|
|
426
|
+
const left = parts[3] ?? right;
|
|
367
427
|
const m = /^(-?\d*\.?\d+)(px)?$/.exec(bottom);
|
|
368
428
|
const base = m ? parseFloat(m[1]) : 0; // non-px bottoms (e.g. %) cannot be combined; offset wins
|
|
369
429
|
return `${top} ${right} ${base - offset}px ${left}`;
|
|
@@ -401,8 +461,15 @@ function getScrollProgress(el, root) {
|
|
|
401
461
|
/* ------------------------------------------------------------------ */
|
|
402
462
|
/** The animation currently running on an element, so it can be cancelled/replaced. */
|
|
403
463
|
const running = new WeakMap();
|
|
464
|
+
/** Native engine: the scroll-linked exit animation, next to the entrance in `running`. */
|
|
465
|
+
const exits = new WeakMap();
|
|
404
466
|
const timers = new WeakMap();
|
|
405
467
|
function cancelRunning(el) {
|
|
468
|
+
const exitAnim = exits.get(el);
|
|
469
|
+
if (exitAnim) {
|
|
470
|
+
exits.delete(el);
|
|
471
|
+
exitAnim.cancel();
|
|
472
|
+
}
|
|
406
473
|
const anim = running.get(el);
|
|
407
474
|
if (anim) {
|
|
408
475
|
running.delete(el);
|
|
@@ -513,7 +580,7 @@ function stopAnimation(el, config = {}) {
|
|
|
513
580
|
function motionDisabled(config) {
|
|
514
581
|
return !!config.disabled || prefersReducedMotion();
|
|
515
582
|
}
|
|
516
|
-
function runAnimation(el, opts, config, staggerIndex = 0) {
|
|
583
|
+
function runAnimation(el, opts, config, staggerIndex = 0, pending) {
|
|
517
584
|
const { duration, delay, stagger, onStart, onComplete } = opts;
|
|
518
585
|
const totalDelay = Math.max(0, delay + staggerIndex * stagger);
|
|
519
586
|
cancelRunning(el);
|
|
@@ -529,8 +596,10 @@ function runAnimation(el, opts, config, staggerIndex = 0) {
|
|
|
529
596
|
el.classList.remove(config.hiddenClass);
|
|
530
597
|
el.classList.add(config.visibleClass);
|
|
531
598
|
onStart(el);
|
|
599
|
+
pending?.add(el);
|
|
532
600
|
timers.set(el, setTimeout(() => {
|
|
533
601
|
timers.delete(el);
|
|
602
|
+
pending?.delete(el);
|
|
534
603
|
onComplete(el);
|
|
535
604
|
}, duration + totalDelay));
|
|
536
605
|
return;
|
|
@@ -555,7 +624,7 @@ function runAnimation(el, opts, config, staggerIndex = 0) {
|
|
|
555
624
|
try {
|
|
556
625
|
anim = el.animate(built.keyframes, timing);
|
|
557
626
|
}
|
|
558
|
-
catch
|
|
627
|
+
catch {
|
|
559
628
|
// Invalid user easing string (WAAPI throws a TypeError): fall back to 'ease'
|
|
560
629
|
anim = el.animate(built.keyframes, { ...timing, easing: 'ease' });
|
|
561
630
|
}
|
|
@@ -570,7 +639,7 @@ function runAnimation(el, opts, config, staggerIndex = 0) {
|
|
|
570
639
|
try {
|
|
571
640
|
anim.commitStyles();
|
|
572
641
|
}
|
|
573
|
-
catch
|
|
642
|
+
catch {
|
|
574
643
|
setStyles(el, preset.to);
|
|
575
644
|
}
|
|
576
645
|
anim.cancel();
|
|
@@ -578,6 +647,69 @@ function runAnimation(el, opts, config, staggerIndex = 0) {
|
|
|
578
647
|
onComplete(el);
|
|
579
648
|
};
|
|
580
649
|
}
|
|
650
|
+
/**
|
|
651
|
+
* Native engine: attach the preset to a `ViewTimeline` of the element, so the
|
|
652
|
+
* browser drives it from scroll position. Returns `null` when that fails
|
|
653
|
+
* (caller falls back to the JS engine).
|
|
654
|
+
*/
|
|
655
|
+
function startNative(el, opts, onFrozen) {
|
|
656
|
+
if (typeof el.animate !== 'function')
|
|
657
|
+
return null;
|
|
658
|
+
const preset = resolvePreset(opts.animation);
|
|
659
|
+
const built = buildAnimation(preset, opts.easing);
|
|
660
|
+
let anim;
|
|
661
|
+
try {
|
|
662
|
+
const timeline = new globalThis.ViewTimeline({ subject: el, axis: 'block' });
|
|
663
|
+
const timing = {
|
|
664
|
+
fill: 'both',
|
|
665
|
+
easing: built.easing,
|
|
666
|
+
timeline,
|
|
667
|
+
rangeStart: opts.viewRange[0],
|
|
668
|
+
rangeEnd: opts.viewRange[1],
|
|
669
|
+
};
|
|
670
|
+
try {
|
|
671
|
+
anim = el.animate(built.keyframes, timing);
|
|
672
|
+
}
|
|
673
|
+
catch {
|
|
674
|
+
anim = el.animate(built.keyframes, { ...timing, easing: 'linear' });
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
catch {
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
running.set(el, anim);
|
|
681
|
+
if (opts.exit) {
|
|
682
|
+
// Reverse keyframes over the `exit` range; `fill: 'forwards'` so it has
|
|
683
|
+
// no effect before the element starts leaving.
|
|
684
|
+
const leave = resolvePreset(opts.exit === true ? opts.animation : opts.exit);
|
|
685
|
+
const out = buildAnimation({ from: leave.to, to: leave.from }, opts.easing);
|
|
686
|
+
try {
|
|
687
|
+
const timeline = new globalThis.ViewTimeline({ subject: el, axis: 'block' });
|
|
688
|
+
const timing = { fill: 'forwards', easing: out.easing, timeline, rangeStart: 'exit 0%', rangeEnd: 'exit 100%' };
|
|
689
|
+
exits.set(el, el.animate(out.keyframes, timing));
|
|
690
|
+
}
|
|
691
|
+
catch {
|
|
692
|
+
// Exit is cosmetic: ignore if unsupported.
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
const keep = opts.repeat || !opts.once;
|
|
696
|
+
anim.onfinish = () => {
|
|
697
|
+
// `once`: freeze the end state, so scrolling back up does not reverse it.
|
|
698
|
+
if (!keep && running.get(el) === anim) {
|
|
699
|
+
running.delete(el);
|
|
700
|
+
try {
|
|
701
|
+
anim.commitStyles();
|
|
702
|
+
}
|
|
703
|
+
catch {
|
|
704
|
+
setStyles(el, preset.to);
|
|
705
|
+
}
|
|
706
|
+
anim.cancel();
|
|
707
|
+
onFrozen?.();
|
|
708
|
+
}
|
|
709
|
+
opts.onComplete(el);
|
|
710
|
+
};
|
|
711
|
+
return anim;
|
|
712
|
+
}
|
|
581
713
|
function applyParallax(el, progress, parallax) {
|
|
582
714
|
const { x = 0, y = 0, rotate = 0, scale = 1, speed = 1 } = parallax;
|
|
583
715
|
const p = (progress - 0.5) * 2 * speed;
|
|
@@ -593,6 +725,26 @@ function applyParallax(el, progress, parallax) {
|
|
|
593
725
|
transform += ` scale(${1 + (scale - 1) * p})`;
|
|
594
726
|
el.style.transform = transform.trim();
|
|
595
727
|
}
|
|
728
|
+
/** Play the exit animation (entrance or `exit` preset, reversed), ending hidden. */
|
|
729
|
+
function runExit(el, opts, config) {
|
|
730
|
+
if (config.useClassNames || typeof el.animate !== 'function') {
|
|
731
|
+
hideElement(el, config);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
cancelRunning(el);
|
|
735
|
+
const preset = resolvePreset(opts.exit === true ? opts.animation : opts.exit);
|
|
736
|
+
const built = buildAnimation({ from: preset.to, to: preset.from }, opts.easing);
|
|
737
|
+
const timing = { duration: opts.duration, easing: built.easing, fill: 'forwards' };
|
|
738
|
+
let anim;
|
|
739
|
+
try {
|
|
740
|
+
anim = el.animate(built.keyframes, timing);
|
|
741
|
+
}
|
|
742
|
+
catch {
|
|
743
|
+
anim = el.animate(built.keyframes, { ...timing, easing: 'ease' });
|
|
744
|
+
}
|
|
745
|
+
// Kept (filling) until the next entrance cancels it.
|
|
746
|
+
running.set(el, anim);
|
|
747
|
+
}
|
|
596
748
|
function hideElement(el, config) {
|
|
597
749
|
cancelRunning(el);
|
|
598
750
|
if (config.useClassNames) {
|
|
@@ -635,6 +787,10 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
635
787
|
let listening = null;
|
|
636
788
|
// Active watch() MutationObservers, disconnected by destroy().
|
|
637
789
|
const watchers = new Set();
|
|
790
|
+
// Elements with a pending class-name completion timer started by this instance.
|
|
791
|
+
const pending = new Set();
|
|
792
|
+
// Elements whose entrance runs on a native scroll-driven timeline.
|
|
793
|
+
const natives = new Set();
|
|
638
794
|
// Observers are shared between elements with the same root/threshold/rootMargin,
|
|
639
795
|
// instead of one (or two) IntersectionObservers per element.
|
|
640
796
|
const pools = new Map();
|
|
@@ -648,14 +804,27 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
648
804
|
return io;
|
|
649
805
|
}
|
|
650
806
|
function teardown(el, restore) {
|
|
651
|
-
var _a;
|
|
652
807
|
const record = registry.get(el);
|
|
653
808
|
if (!record)
|
|
654
809
|
return;
|
|
655
810
|
record.observer.unobserve(el);
|
|
656
|
-
|
|
811
|
+
record.progressObserver?.unobserve(el);
|
|
657
812
|
registry.delete(el);
|
|
658
813
|
untrack(el);
|
|
814
|
+
if (record.engine === 'css') {
|
|
815
|
+
// A scroll-linked animation would keep following the scroll: stop it and
|
|
816
|
+
// show the element (unless it finished and is just being released).
|
|
817
|
+
if (restore) {
|
|
818
|
+
natives.delete(el);
|
|
819
|
+
reveal(el, config);
|
|
820
|
+
}
|
|
821
|
+
else if (el.isConnected === false) {
|
|
822
|
+
// Left the DOM: drop it too, or the instance keeps it alive (and
|
|
823
|
+
// destroy() touches it) for its whole lifetime.
|
|
824
|
+
natives.delete(el);
|
|
825
|
+
}
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
659
828
|
// An element that never animated would otherwise stay invisible forever.
|
|
660
829
|
if (restore && !record.animated)
|
|
661
830
|
reveal(el, config);
|
|
@@ -724,7 +893,6 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
724
893
|
function onIntersect(entries) {
|
|
725
894
|
const staggerCounts = new Map();
|
|
726
895
|
entries.forEach((entry) => {
|
|
727
|
-
var _a;
|
|
728
896
|
const el = entry.target;
|
|
729
897
|
const record = registry.get(el);
|
|
730
898
|
if (!record)
|
|
@@ -734,6 +902,29 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
734
902
|
teardown(el, false);
|
|
735
903
|
return;
|
|
736
904
|
}
|
|
905
|
+
if (record.engine === 'css') {
|
|
906
|
+
// The browser drives the animation; only lifecycle bookkeeping here.
|
|
907
|
+
if (entry.isIntersecting) {
|
|
908
|
+
opts.onEnter(el);
|
|
909
|
+
if (!record.animated) {
|
|
910
|
+
record.animated = true;
|
|
911
|
+
opts.onStart(el);
|
|
912
|
+
}
|
|
913
|
+
if (opts.once && !opts.repeat) {
|
|
914
|
+
record.observer.unobserve(el);
|
|
915
|
+
if (config.autoUnregister && !record.progressObserver) {
|
|
916
|
+
finished.add(el);
|
|
917
|
+
teardown(el, false);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
else {
|
|
922
|
+
opts.onLeave(el);
|
|
923
|
+
if (opts.repeat)
|
|
924
|
+
record.animated = false;
|
|
925
|
+
}
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
737
928
|
if (entry.isIntersecting) {
|
|
738
929
|
opts.onEnter(el);
|
|
739
930
|
if (record.animated && !opts.repeat)
|
|
@@ -743,10 +934,10 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
743
934
|
let staggerIndex = 0;
|
|
744
935
|
if (opts.stagger > 0) {
|
|
745
936
|
const parent = el.parentElement;
|
|
746
|
-
staggerIndex =
|
|
937
|
+
staggerIndex = staggerCounts.get(parent) ?? 0;
|
|
747
938
|
staggerCounts.set(parent, staggerIndex + 1);
|
|
748
939
|
}
|
|
749
|
-
runAnimation(el, opts, config, staggerIndex);
|
|
940
|
+
runAnimation(el, opts, config, staggerIndex, pending);
|
|
750
941
|
record.animated = true;
|
|
751
942
|
if (opts.once && !opts.repeat) {
|
|
752
943
|
// Keep the progress observer: parallax/onProgress must keep working.
|
|
@@ -762,8 +953,12 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
762
953
|
else {
|
|
763
954
|
opts.onLeave(el);
|
|
764
955
|
if (opts.repeat && record.animated) {
|
|
765
|
-
if (!motionDisabled(config))
|
|
766
|
-
|
|
956
|
+
if (!motionDisabled(config)) {
|
|
957
|
+
if (opts.exit)
|
|
958
|
+
runExit(el, opts, config);
|
|
959
|
+
else
|
|
960
|
+
hideElement(el, config);
|
|
961
|
+
}
|
|
767
962
|
record.animated = false;
|
|
768
963
|
}
|
|
769
964
|
}
|
|
@@ -797,12 +992,14 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
797
992
|
}));
|
|
798
993
|
}
|
|
799
994
|
function attach(el, record, observeMain) {
|
|
800
|
-
var _a;
|
|
801
995
|
record.observer = getObserver(record.options);
|
|
802
996
|
if (observeMain)
|
|
803
997
|
record.observer.observe(el);
|
|
804
998
|
record.progressObserver = needsProgress(record.options) ? getProgressObserver(record.options) : undefined;
|
|
805
|
-
|
|
999
|
+
record.progressObserver?.observe(el);
|
|
1000
|
+
}
|
|
1001
|
+
function wantsNative(opts) {
|
|
1002
|
+
return opts.engine !== 'js' && !config.useClassNames && !motionDisabled(config) && supportsScrollTimeline();
|
|
806
1003
|
}
|
|
807
1004
|
function observeElement(el, opts) {
|
|
808
1005
|
if (registry.has(el) || finished.has(el))
|
|
@@ -812,9 +1009,16 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
812
1009
|
reveal(el, config);
|
|
813
1010
|
return;
|
|
814
1011
|
}
|
|
815
|
-
|
|
1012
|
+
const record = { element: el, options: opts, animated: false, engine: 'js' };
|
|
1013
|
+
if (wantsNative(opts)) {
|
|
1014
|
+
cancelRunning(el);
|
|
1015
|
+
if (startNative(el, opts, () => natives.delete(el))) {
|
|
1016
|
+
record.engine = 'css';
|
|
1017
|
+
natives.add(el);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
if (record.engine === 'js' && !motionDisabled(config))
|
|
816
1021
|
hideElement(el, config);
|
|
817
|
-
const record = { element: el, options: opts, animated: false };
|
|
818
1022
|
registry.set(el, record);
|
|
819
1023
|
attach(el, record, true);
|
|
820
1024
|
}
|
|
@@ -835,14 +1039,14 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
835
1039
|
});
|
|
836
1040
|
},
|
|
837
1041
|
init(rootElement) {
|
|
838
|
-
const scope = rootElement
|
|
1042
|
+
const scope = rootElement ?? (hasDOM() ? document : null);
|
|
839
1043
|
if (!scope)
|
|
840
1044
|
return;
|
|
841
1045
|
pruneDetached();
|
|
842
1046
|
scope.querySelectorAll('[data-sa]').forEach(observeDataElement);
|
|
843
1047
|
},
|
|
844
1048
|
watch(rootElement) {
|
|
845
|
-
const scope = rootElement
|
|
1049
|
+
const scope = rootElement ?? (hasDOM() ? document : null);
|
|
846
1050
|
if (!scope || typeof MutationObserver === 'undefined')
|
|
847
1051
|
return noop;
|
|
848
1052
|
instance.init(scope);
|
|
@@ -881,10 +1085,25 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
881
1085
|
destroy() {
|
|
882
1086
|
watchers.forEach((mo) => mo.disconnect());
|
|
883
1087
|
watchers.clear();
|
|
1088
|
+
// Class-name mode: drop completion timers so onComplete never fires after destroy().
|
|
1089
|
+
pending.forEach((el) => {
|
|
1090
|
+
const timer = timers.get(el);
|
|
1091
|
+
if (timer !== undefined) {
|
|
1092
|
+
clearTimeout(timer);
|
|
1093
|
+
timers.delete(el);
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
1096
|
+
pending.clear();
|
|
884
1097
|
registry.forEach((record, el) => {
|
|
885
|
-
if (!record.animated)
|
|
1098
|
+
if (!record.animated && record.engine !== 'css')
|
|
1099
|
+
reveal(el, config);
|
|
1100
|
+
});
|
|
1101
|
+
// Native scroll-linked animations still running: stop them, show content.
|
|
1102
|
+
natives.forEach((el) => {
|
|
1103
|
+
if (running.has(el))
|
|
886
1104
|
reveal(el, config);
|
|
887
1105
|
});
|
|
1106
|
+
natives.clear();
|
|
888
1107
|
pools.forEach((pool) => pool.forEach((io) => io.disconnect()));
|
|
889
1108
|
pools.clear();
|
|
890
1109
|
registry.clear();
|
|
@@ -909,7 +1128,7 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
909
1128
|
},
|
|
910
1129
|
animate(target, options = {}) {
|
|
911
1130
|
const opts = mergeOptions(options, config);
|
|
912
|
-
resolveTargets(target).forEach((el) => runAnimation(el, opts, config, 0));
|
|
1131
|
+
resolveTargets(target).forEach((el) => runAnimation(el, opts, config, 0, pending));
|
|
913
1132
|
},
|
|
914
1133
|
getObservedElements() {
|
|
915
1134
|
return Array.from(registry.values());
|
|
@@ -921,329 +1140,18 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
921
1140
|
return instance;
|
|
922
1141
|
}
|
|
923
1142
|
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
return () => undefined; // leave content visible
|
|
940
|
-
const sa = instance || fallback$1 || (fallback$1 = createScrollAnimate());
|
|
941
|
-
const { stagger = 80, delay = 0, threshold = 0.1, rootMargin = '0px', observeChildren = false, ...rest } = options;
|
|
942
|
-
let items = Array.from(container.children);
|
|
943
|
-
let revealed = false;
|
|
944
|
-
const late = [];
|
|
945
|
-
items.forEach((child) => prepareElement(child));
|
|
946
|
-
const io = new IntersectionObserver((entries) => {
|
|
947
|
-
if (revealed || !entries.some((entry) => entry.isIntersecting))
|
|
948
|
-
return;
|
|
949
|
-
revealed = true;
|
|
950
|
-
io.disconnect();
|
|
951
|
-
items.forEach((child, i) => {
|
|
952
|
-
if (child.parentNode === container)
|
|
953
|
-
sa.animate(child, { ...rest, delay: delay + i * stagger });
|
|
954
|
-
});
|
|
955
|
-
items = [];
|
|
956
|
-
}, { threshold, rootMargin });
|
|
957
|
-
io.observe(container);
|
|
958
|
-
let mo;
|
|
959
|
-
if (observeChildren && typeof MutationObserver !== 'undefined') {
|
|
960
|
-
mo = new MutationObserver((records) => {
|
|
961
|
-
records.forEach((record) => {
|
|
962
|
-
record.addedNodes.forEach((node) => {
|
|
963
|
-
if (!(node instanceof Element) || node.parentNode !== container)
|
|
964
|
-
return;
|
|
965
|
-
if (!revealed) {
|
|
966
|
-
prepareElement(node);
|
|
967
|
-
items.push(node);
|
|
968
|
-
}
|
|
969
|
-
else {
|
|
970
|
-
// The core engine staggers siblings relative to the batch that
|
|
971
|
-
// enters the viewport together.
|
|
972
|
-
late.push(node);
|
|
973
|
-
sa.observe(node, { ...rest, delay, stagger, threshold, rootMargin });
|
|
974
|
-
}
|
|
975
|
-
});
|
|
976
|
-
record.removedNodes.forEach((node) => {
|
|
977
|
-
if (!(node instanceof Element))
|
|
978
|
-
return;
|
|
979
|
-
items = items.filter((el) => el !== node);
|
|
980
|
-
const i = late.indexOf(node);
|
|
981
|
-
if (i >= 0) {
|
|
982
|
-
late.splice(i, 1);
|
|
983
|
-
sa.unobserve(node);
|
|
984
|
-
}
|
|
985
|
-
});
|
|
986
|
-
});
|
|
987
|
-
});
|
|
988
|
-
mo.observe(container, { childList: true });
|
|
989
|
-
}
|
|
990
|
-
return () => {
|
|
991
|
-
io.disconnect();
|
|
992
|
-
mo === null || mo === void 0 ? void 0 : mo.disconnect();
|
|
993
|
-
// Stopped before the container was revealed: never leave the children hidden.
|
|
994
|
-
if (!revealed) {
|
|
995
|
-
revealed = true;
|
|
996
|
-
items.forEach((child) => stopAnimation(child));
|
|
997
|
-
items = [];
|
|
998
|
-
}
|
|
999
|
-
late.forEach((el) => sa.unobserve(el));
|
|
1000
|
-
late.length = 0;
|
|
1001
|
-
};
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
/**
|
|
1005
|
-
* use-scroll-animate - Sequence / timeline helper
|
|
1006
|
-
* Chain animations on several targets, one after another (or overlapping).
|
|
1007
|
-
*/
|
|
1008
|
-
let fallback = null;
|
|
1009
|
-
function plan(steps, defaults) {
|
|
1010
|
-
const out = [];
|
|
1011
|
-
let cursor = 0;
|
|
1012
|
-
steps.forEach((step) => {
|
|
1013
|
-
var _a, _b;
|
|
1014
|
-
const { target, gap = 0, at, ...stepOpts } = step;
|
|
1015
|
-
const opts = { ...defaults, ...stepOpts };
|
|
1016
|
-
const duration = (_a = opts.duration) !== null && _a !== void 0 ? _a : 600;
|
|
1017
|
-
const start = Math.max(0, at !== null && at !== void 0 ? at : cursor + gap) + ((_b = opts.delay) !== null && _b !== void 0 ? _b : 0);
|
|
1018
|
-
let end = Math.max(cursor, start);
|
|
1019
|
-
resolveTargets(target).forEach((el, i) => {
|
|
1020
|
-
var _a;
|
|
1021
|
-
const delay = start + i * ((_a = opts.stagger) !== null && _a !== void 0 ? _a : 0);
|
|
1022
|
-
out.push({ el, opts: { ...opts, duration, delay, stagger: 0 }, end: delay + duration });
|
|
1023
|
-
end = Math.max(end, delay + duration);
|
|
1024
|
-
});
|
|
1025
|
-
cursor = end;
|
|
1026
|
-
});
|
|
1027
|
-
return out;
|
|
1028
|
-
}
|
|
1029
|
-
/**
|
|
1030
|
-
* Build a timeline of animations.
|
|
1031
|
-
*
|
|
1032
|
-
* @example
|
|
1033
|
-
* sequence([
|
|
1034
|
-
* { target: '.title', animation: 'fade-in-up' },
|
|
1035
|
-
* { target: '.subtitle', animation: 'blur-in', gap: -300 }, // overlap by 300ms
|
|
1036
|
-
* { target: '.card', animation: 'scale-up', stagger: 80 },
|
|
1037
|
-
* ], { trigger: '.hero' });
|
|
1038
|
-
*/
|
|
1039
|
-
function sequence(steps, options = {}) {
|
|
1040
|
-
var _a, _b;
|
|
1041
|
-
const { trigger, instance, ...defaults } = options;
|
|
1042
|
-
const sa = () => instance || fallback || (fallback = createScrollAnimate());
|
|
1043
|
-
let io;
|
|
1044
|
-
let active = [];
|
|
1045
|
-
let settle;
|
|
1046
|
-
// Targets hidden while waiting for `trigger`; revealed if cancelled before it fires.
|
|
1047
|
-
let prepared = [];
|
|
1048
|
-
const controller = {
|
|
1049
|
-
play() {
|
|
1050
|
-
controller.cancel();
|
|
1051
|
-
if (!hasDOM())
|
|
1052
|
-
return Promise.resolve();
|
|
1053
|
-
active = plan(steps, defaults);
|
|
1054
|
-
return new Promise((resolve) => {
|
|
1055
|
-
let left = active.length;
|
|
1056
|
-
settle = () => {
|
|
1057
|
-
settle = undefined;
|
|
1058
|
-
resolve();
|
|
1059
|
-
};
|
|
1060
|
-
if (!left)
|
|
1061
|
-
return settle();
|
|
1062
|
-
const run = active;
|
|
1063
|
-
run.forEach(({ el, opts }) => {
|
|
1064
|
-
const done = opts.onComplete;
|
|
1065
|
-
sa().animate(el, {
|
|
1066
|
-
...opts,
|
|
1067
|
-
onComplete: (node) => {
|
|
1068
|
-
done === null || done === void 0 ? void 0 : done(node);
|
|
1069
|
-
if (run === active && --left === 0)
|
|
1070
|
-
settle === null || settle === void 0 ? void 0 : settle();
|
|
1071
|
-
},
|
|
1072
|
-
});
|
|
1073
|
-
});
|
|
1074
|
-
});
|
|
1075
|
-
},
|
|
1076
|
-
cancel() {
|
|
1077
|
-
io === null || io === void 0 ? void 0 : io.disconnect();
|
|
1078
|
-
io = undefined;
|
|
1079
|
-
prepared.forEach((el) => stopAnimation(el));
|
|
1080
|
-
prepared = [];
|
|
1081
|
-
active.forEach(({ el }) => stopAnimation(el));
|
|
1082
|
-
active = [];
|
|
1083
|
-
settle === null || settle === void 0 ? void 0 : settle();
|
|
1084
|
-
},
|
|
1085
|
-
duration() {
|
|
1086
|
-
return plan(steps, defaults).reduce((max, p) => Math.max(max, p.end), 0);
|
|
1087
|
-
},
|
|
1088
|
-
};
|
|
1089
|
-
if (trigger && hasDOM() && supportsObserver()) {
|
|
1090
|
-
const el = resolveTargets(trigger)[0];
|
|
1091
|
-
if (el) {
|
|
1092
|
-
prepared = plan(steps, defaults).map(({ el: target }) => target);
|
|
1093
|
-
prepared.forEach((target) => prepareElement(target));
|
|
1094
|
-
io = new IntersectionObserver((entries) => {
|
|
1095
|
-
if (!entries.some((e) => e.isIntersecting))
|
|
1096
|
-
return;
|
|
1097
|
-
io === null || io === void 0 ? void 0 : io.disconnect();
|
|
1098
|
-
io = undefined;
|
|
1099
|
-
prepared = []; // play() takes over from here
|
|
1100
|
-
controller.play();
|
|
1101
|
-
}, { threshold: (_a = defaults.threshold) !== null && _a !== void 0 ? _a : 0.1, rootMargin: (_b = defaults.rootMargin) !== null && _b !== void 0 ? _b : '0px' });
|
|
1102
|
-
io.observe(el);
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
return controller;
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
/**
|
|
1109
|
-
* use-scroll-animate - React Integration
|
|
1110
|
-
* Provides useScrollAnimate and useScrollStagger hooks for React applications.
|
|
1111
|
-
* `useScrollStagger({ observeChildren: true })` also animates children added later.
|
|
1112
|
-
*
|
|
1113
|
-
* Both hooks are thin wrappers around the core engine, so they share its
|
|
1114
|
-
* behaviour: `once`, `offset`, custom easing functions, parallax,
|
|
1115
|
-
* `prefers-reduced-motion` support, and proper cleanup on unmount.
|
|
1116
|
-
*/
|
|
1117
|
-
const CALLBACKS = ['onStart', 'onComplete', 'onEnter', 'onLeave', 'onProgress'];
|
|
1118
|
-
/**
|
|
1119
|
-
* Wrap the callbacks that exist at mount so they always call the latest
|
|
1120
|
-
* version from the most recent render (avoids stale closures without
|
|
1121
|
-
* re-creating observers on every render).
|
|
1122
|
-
* @internal
|
|
1123
|
-
*/
|
|
1124
|
-
function withLatestCallbacks(latest) {
|
|
1125
|
-
const initial = latest.current || {};
|
|
1126
|
-
const opts = { ...initial };
|
|
1127
|
-
CALLBACKS.forEach((name) => {
|
|
1128
|
-
if (typeof initial[name] === 'function') {
|
|
1129
|
-
opts[name] = (...args) => { var _a, _b; return (_b = (_a = latest.current) === null || _a === void 0 ? void 0 : _a[name]) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); };
|
|
1130
|
-
}
|
|
1131
|
-
});
|
|
1132
|
-
return opts;
|
|
1133
|
-
}
|
|
1134
|
-
function createReactHooks(React) {
|
|
1135
|
-
// Created lazily on the client so importing on the server is side-effect free.
|
|
1136
|
-
let instance = null;
|
|
1137
|
-
const getInstance = () => instance || (instance = createScrollAnimate());
|
|
1138
|
-
function useScrollAnimate(options = {}) {
|
|
1139
|
-
const ref = React.useRef(null);
|
|
1140
|
-
const optionsRef = React.useRef(options);
|
|
1141
|
-
optionsRef.current = options;
|
|
1142
|
-
React.useEffect(() => {
|
|
1143
|
-
const el = ref.current;
|
|
1144
|
-
if (!el)
|
|
1145
|
-
return;
|
|
1146
|
-
const sa = getInstance();
|
|
1147
|
-
sa.observe(el, withLatestCallbacks(optionsRef));
|
|
1148
|
-
return () => sa.unobserve(el);
|
|
1149
|
-
}, []);
|
|
1150
|
-
return ref;
|
|
1151
|
-
}
|
|
1152
|
-
function useScrollStagger(options = {}) {
|
|
1153
|
-
const ref = React.useRef(null);
|
|
1154
|
-
const optionsRef = React.useRef(options);
|
|
1155
|
-
optionsRef.current = options;
|
|
1156
|
-
React.useEffect(() => {
|
|
1157
|
-
const container = ref.current;
|
|
1158
|
-
if (!container)
|
|
1159
|
-
return;
|
|
1160
|
-
return staggerChildren(container, withLatestCallbacks(optionsRef), getInstance());
|
|
1161
|
-
}, []);
|
|
1162
|
-
return ref;
|
|
1163
|
-
}
|
|
1164
|
-
return { useScrollAnimate, useScrollStagger };
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
/**
|
|
1168
|
-
* use-scroll-animate - Vue 3 Integration
|
|
1169
|
-
* Provides useScrollAnimate and useScrollStagger composables for Vue 3 applications.
|
|
1170
|
-
*
|
|
1171
|
-
* A thin wrapper around the core engine, so it shares its behaviour: `once`,
|
|
1172
|
-
* `offset`, custom easing functions, parallax, `prefers-reduced-motion`
|
|
1173
|
-
* support, and cleanup on unmount.
|
|
1174
|
-
*/
|
|
1175
|
-
/** Support refs on components (`$el`) as well as plain elements. */
|
|
1176
|
-
function unwrap(value) {
|
|
1177
|
-
if (value && typeof Element !== 'undefined' && !(value instanceof Element) && value.$el instanceof Element) {
|
|
1178
|
-
return value.$el;
|
|
1179
|
-
}
|
|
1180
|
-
return value || null;
|
|
1181
|
-
}
|
|
1182
|
-
function createVueComposables(Vue) {
|
|
1183
|
-
// Created lazily on the client so importing on the server is side-effect free.
|
|
1184
|
-
let instance = null;
|
|
1185
|
-
const getInstance = () => instance || (instance = createScrollAnimate());
|
|
1186
|
-
function useScrollAnimate(options = {}) {
|
|
1187
|
-
const animateRef = Vue.ref(null);
|
|
1188
|
-
let el = null;
|
|
1189
|
-
Vue.onMounted(() => {
|
|
1190
|
-
const target = unwrap(animateRef.value);
|
|
1191
|
-
if (!target)
|
|
1192
|
-
return;
|
|
1193
|
-
el = target;
|
|
1194
|
-
getInstance().observe(el, options);
|
|
1195
|
-
});
|
|
1196
|
-
Vue.onUnmounted(() => {
|
|
1197
|
-
if (el)
|
|
1198
|
-
getInstance().unobserve(el);
|
|
1199
|
-
el = null;
|
|
1200
|
-
});
|
|
1201
|
-
return { animateRef };
|
|
1202
|
-
}
|
|
1203
|
-
/** Stagger the children of `staggerRef`; `observeChildren: true` also animates children added later. */
|
|
1204
|
-
function useScrollStagger(options = {}) {
|
|
1205
|
-
const staggerRef = Vue.ref(null);
|
|
1206
|
-
let stop;
|
|
1207
|
-
Vue.onMounted(() => {
|
|
1208
|
-
const target = unwrap(staggerRef.value);
|
|
1209
|
-
if (target)
|
|
1210
|
-
stop = staggerChildren(target, options, getInstance());
|
|
1211
|
-
});
|
|
1212
|
-
Vue.onUnmounted(() => {
|
|
1213
|
-
stop === null || stop === void 0 ? void 0 : stop();
|
|
1214
|
-
stop = undefined;
|
|
1215
|
-
});
|
|
1216
|
-
return { staggerRef };
|
|
1217
|
-
}
|
|
1218
|
-
return { useScrollAnimate, useScrollStagger };
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
/**
|
|
1222
|
-
* use-scroll-animate
|
|
1223
|
-
*
|
|
1224
|
-
* A lightweight, dependency-free scroll animation library for modern web
|
|
1225
|
-
* applications. Built with TypeScript, powered by IntersectionObserver and
|
|
1226
|
-
* the Web Animations API. Safe to import during SSR.
|
|
1227
|
-
*
|
|
1228
|
-
* @license MIT
|
|
1229
|
-
* @see https://github.com/HarrisonCN/use-scroll-animate
|
|
1230
|
-
*/
|
|
1231
|
-
/**
|
|
1232
|
-
* Default singleton instance of ScrollAnimate.
|
|
1233
|
-
* Ready to use out of the box with sensible defaults.
|
|
1234
|
-
*
|
|
1235
|
-
* @example
|
|
1236
|
-
* ```js
|
|
1237
|
-
* import ScrollAnimate from 'use-scroll-animate';
|
|
1238
|
-
*
|
|
1239
|
-
* // Auto-initialize all elements with data-sa attribute
|
|
1240
|
-
* ScrollAnimate.init();
|
|
1241
|
-
*
|
|
1242
|
-
* // Or manually observe elements
|
|
1243
|
-
* ScrollAnimate.observe('.my-element', { animation: 'fade-in-up' });
|
|
1244
|
-
* ```
|
|
1245
|
-
*/
|
|
1246
|
-
const ScrollAnimate = createScrollAnimate();
|
|
1247
|
-
|
|
1248
|
-
export { EASING_MAP, PRESETS, createReactHooks, createScrollAnimate, createVueComposables, ScrollAnimate as default, getScrollProgress, resolveEasing, resolvePreset, sequence, staggerChildren };
|
|
1249
|
-
//# sourceMappingURL=index.mjs.map
|
|
1143
|
+
exports.EASING_MAP = EASING_MAP;
|
|
1144
|
+
exports.PRESETS = PRESETS;
|
|
1145
|
+
exports.createScrollAnimate = createScrollAnimate;
|
|
1146
|
+
exports.getScrollProgress = getScrollProgress;
|
|
1147
|
+
exports.hasDOM = hasDOM;
|
|
1148
|
+
exports.prefersReducedMotion = prefersReducedMotion;
|
|
1149
|
+
exports.prepareElement = prepareElement;
|
|
1150
|
+
exports.readOptions = readOptions;
|
|
1151
|
+
exports.resolveEasing = resolveEasing;
|
|
1152
|
+
exports.resolvePreset = resolvePreset;
|
|
1153
|
+
exports.resolveTargets = resolveTargets;
|
|
1154
|
+
exports.stopAnimation = stopAnimation;
|
|
1155
|
+
exports.supportsObserver = supportsObserver;
|
|
1156
|
+
exports.supportsScrollTimeline = supportsScrollTimeline;
|
|
1157
|
+
//# sourceMappingURL=core-B_J4rXbC.cjs.map
|