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
|
@@ -138,9 +138,8 @@ const PRESETS = {
|
|
|
138
138
|
},
|
|
139
139
|
};
|
|
140
140
|
function resolvePreset(animation) {
|
|
141
|
-
var _a;
|
|
142
141
|
if (typeof animation === 'string') {
|
|
143
|
-
return
|
|
142
|
+
return PRESETS[animation.trim()] ?? PRESETS['fade-in-up'];
|
|
144
143
|
}
|
|
145
144
|
if (Array.isArray(animation)) {
|
|
146
145
|
const combined = { from: {}, to: {} };
|
|
@@ -181,9 +180,8 @@ const EASING_MAP = {
|
|
|
181
180
|
'heavy-bounce': 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
|
|
182
181
|
};
|
|
183
182
|
function resolveEasing(easing) {
|
|
184
|
-
var _a;
|
|
185
183
|
if (typeof easing === 'string') {
|
|
186
|
-
return
|
|
184
|
+
return EASING_MAP[easing] ?? easing;
|
|
187
185
|
}
|
|
188
186
|
if (Array.isArray(easing)) {
|
|
189
187
|
return `cubic-bezier(${easing.join(', ')})`;
|
|
@@ -217,6 +215,7 @@ const DEFAULT_CONFIG = {
|
|
|
217
215
|
disabled: false,
|
|
218
216
|
root: null,
|
|
219
217
|
autoUnregister: true,
|
|
218
|
+
defaultEngine: 'auto',
|
|
220
219
|
};
|
|
221
220
|
const noop = () => undefined;
|
|
222
221
|
/* ------------------------------------------------------------------ */
|
|
@@ -236,6 +235,23 @@ function prefersReducedMotion() {
|
|
|
236
235
|
}
|
|
237
236
|
return !!reducedMotionQuery && reducedMotionQuery.matches;
|
|
238
237
|
}
|
|
238
|
+
let scrollTimelineSupported;
|
|
239
|
+
/**
|
|
240
|
+
* Whether the browser can run presets on a native scroll-driven timeline:
|
|
241
|
+
* `CSS.supports('animation-timeline: view()')` plus the `ViewTimeline`
|
|
242
|
+
* constructor used to attach it from JavaScript. Cached. SSR-safe.
|
|
243
|
+
*/
|
|
244
|
+
function supportsScrollTimeline() {
|
|
245
|
+
if (scrollTimelineSupported === undefined) {
|
|
246
|
+
scrollTimelineSupported =
|
|
247
|
+
hasDOM() &&
|
|
248
|
+
typeof CSS !== 'undefined' &&
|
|
249
|
+
typeof CSS.supports === 'function' &&
|
|
250
|
+
CSS.supports('animation-timeline: view()') &&
|
|
251
|
+
typeof globalThis.ViewTimeline === 'function';
|
|
252
|
+
}
|
|
253
|
+
return scrollTimelineSupported;
|
|
254
|
+
}
|
|
239
255
|
/* ------------------------------------------------------------------ */
|
|
240
256
|
/* Option parsing */
|
|
241
257
|
/* ------------------------------------------------------------------ */
|
|
@@ -253,6 +269,7 @@ function lengthValue(value) {
|
|
|
253
269
|
return /^-?(\d+\.?\d*|\.\d+)$/.test(v) ? parseFloat(v) : v;
|
|
254
270
|
}
|
|
255
271
|
const DEFAULT_PROGRESS_VAR = '--sa-progress';
|
|
272
|
+
const DEFAULT_VIEW_RANGE = ['entry 0%', 'entry 100%'];
|
|
256
273
|
/** `''` (bare attribute) -> default name; `sa-progress` -> `--sa-progress`. */
|
|
257
274
|
function normalizeVar(name) {
|
|
258
275
|
const v = name.trim();
|
|
@@ -260,23 +277,28 @@ function normalizeVar(name) {
|
|
|
260
277
|
return DEFAULT_PROGRESS_VAR;
|
|
261
278
|
return v.startsWith('--') ? v : `--${v}`;
|
|
262
279
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
280
|
+
/**
|
|
281
|
+
* @internal Read options from string attributes. `get(name)` returns the raw
|
|
282
|
+
* value of the attribute `name` (kebab-case, e.g. `root-margin`), `''` for a
|
|
283
|
+
* bare attribute and `undefined` when absent. Shared by `data-sa-*` and the
|
|
284
|
+
* `<scroll-animate>` element.
|
|
285
|
+
*/
|
|
286
|
+
function readOptions(get) {
|
|
266
287
|
const opts = {};
|
|
267
|
-
|
|
268
|
-
|
|
288
|
+
const anim = get('animation');
|
|
289
|
+
if (anim) {
|
|
269
290
|
opts.animation = (anim.includes(',') ? anim.split(',').map((s) => s.trim()) : anim.trim());
|
|
270
291
|
}
|
|
271
|
-
opts.duration = num(
|
|
272
|
-
opts.delay = num(
|
|
273
|
-
|
|
274
|
-
|
|
292
|
+
opts.duration = num(get('duration'));
|
|
293
|
+
opts.delay = num(get('delay'));
|
|
294
|
+
const easing = get('easing');
|
|
295
|
+
if (easing) {
|
|
296
|
+
const e = easing.trim();
|
|
275
297
|
if (e.startsWith('[')) {
|
|
276
298
|
try {
|
|
277
299
|
opts.easing = JSON.parse(e);
|
|
278
300
|
}
|
|
279
|
-
catch
|
|
301
|
+
catch {
|
|
280
302
|
// Malformed JSON: fall back to the default easing instead of throwing
|
|
281
303
|
}
|
|
282
304
|
}
|
|
@@ -284,58 +306,95 @@ function parseDataAttributes(el, config) {
|
|
|
284
306
|
opts.easing = e;
|
|
285
307
|
}
|
|
286
308
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
opts.threshold = t.includes(',')
|
|
290
|
-
? t.split(',').map(parseFloat).filter(Number.isFinite)
|
|
291
|
-
: num(t);
|
|
309
|
+
const t = get('threshold');
|
|
310
|
+
if (t) {
|
|
311
|
+
opts.threshold = t.includes(',') ? t.split(',').map(parseFloat).filter(Number.isFinite) : num(t);
|
|
292
312
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if (
|
|
298
|
-
opts.
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
if (
|
|
313
|
+
const rootMargin = get('root-margin');
|
|
314
|
+
if (rootMargin)
|
|
315
|
+
opts.rootMargin = rootMargin;
|
|
316
|
+
const repeat = get('repeat');
|
|
317
|
+
if (repeat !== undefined)
|
|
318
|
+
opts.repeat = repeat !== 'false';
|
|
319
|
+
const once = get('once');
|
|
320
|
+
if (once !== undefined)
|
|
321
|
+
opts.once = once !== 'false';
|
|
322
|
+
opts.offset = num(get('offset'));
|
|
323
|
+
opts.stagger = num(get('stagger'));
|
|
324
|
+
const progressVar = get('progress-var');
|
|
325
|
+
if (progressVar !== undefined)
|
|
326
|
+
opts.progressVar = normalizeVar(progressVar);
|
|
327
|
+
const engine = get('engine')?.trim();
|
|
328
|
+
if (engine === 'auto' || engine === 'js' || engine === 'css')
|
|
329
|
+
opts.engine = engine;
|
|
330
|
+
const range = get('view-range');
|
|
331
|
+
if (range) {
|
|
332
|
+
const [start, end] = range.split(',').map((s) => s.trim());
|
|
333
|
+
if (start && end)
|
|
334
|
+
opts.viewRange = [start, end];
|
|
335
|
+
}
|
|
336
|
+
const exit = get('exit');
|
|
337
|
+
if (exit !== undefined) {
|
|
338
|
+
const e = exit.trim();
|
|
339
|
+
opts.exit = e === '' || e === 'true' ? true : e === 'false' ? false : (e.includes(',') ? e.split(',').map((s) => s.trim()) : e);
|
|
340
|
+
}
|
|
341
|
+
const progress = get('progress');
|
|
342
|
+
if (progress)
|
|
343
|
+
opts.progressMode = progress.trim() === 'scroll' ? 'scroll' : 'ratio';
|
|
344
|
+
const px = get('parallax-x');
|
|
345
|
+
const py = get('parallax-y');
|
|
346
|
+
const pr = get('parallax-rotate');
|
|
347
|
+
const ps = get('parallax-scale');
|
|
348
|
+
if (px || py || pr || ps) {
|
|
306
349
|
opts.parallax = {
|
|
307
|
-
x: lengthValue(
|
|
308
|
-
y: lengthValue(
|
|
309
|
-
rotate: num(
|
|
310
|
-
scale: num(
|
|
311
|
-
speed:
|
|
350
|
+
x: lengthValue(px),
|
|
351
|
+
y: lengthValue(py),
|
|
352
|
+
rotate: num(pr),
|
|
353
|
+
scale: num(ps),
|
|
354
|
+
speed: num(get('parallax-speed')) ?? 1,
|
|
312
355
|
};
|
|
313
356
|
}
|
|
314
|
-
|
|
357
|
+
// Drop keys that were not set, so they don't override defaults when spread.
|
|
358
|
+
Object.keys(opts).forEach((k) => opts[k] === undefined && delete opts[k]);
|
|
359
|
+
return opts;
|
|
360
|
+
}
|
|
361
|
+
function parseDataAttributes(el, config) {
|
|
362
|
+
return mergeOptions(readOptions((name) => {
|
|
363
|
+
const v = el.getAttribute(`data-sa-${name}`);
|
|
364
|
+
return v === null ? undefined : v;
|
|
365
|
+
}), config);
|
|
315
366
|
}
|
|
316
367
|
function mergeOptions(opts, config) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
368
|
+
let engine = opts.engine ?? config.defaultEngine;
|
|
369
|
+
// 'auto' keeps time-based semantics when the element asks for them explicitly.
|
|
370
|
+
if (engine === 'auto' && (opts.duration !== undefined || opts.delay !== undefined || opts.offset !== undefined || (opts.stagger ?? 0) > 0)) {
|
|
371
|
+
engine = 'js';
|
|
372
|
+
}
|
|
373
|
+
const exit = opts.exit ?? false;
|
|
374
|
+
const repeat = opts.repeat ?? (exit ? true : config.defaultRepeat);
|
|
375
|
+
const threshold = opts.threshold ?? config.defaultThreshold;
|
|
320
376
|
return {
|
|
321
|
-
animation:
|
|
322
|
-
duration:
|
|
323
|
-
delay:
|
|
324
|
-
easing:
|
|
377
|
+
animation: opts.animation ?? config.defaultAnimation,
|
|
378
|
+
duration: opts.duration ?? config.defaultDuration,
|
|
379
|
+
delay: opts.delay ?? config.defaultDelay,
|
|
380
|
+
easing: opts.easing ?? config.defaultEasing,
|
|
325
381
|
threshold: Array.isArray(threshold) && threshold.length === 0 ? config.defaultThreshold : threshold,
|
|
326
|
-
rootMargin:
|
|
382
|
+
rootMargin: opts.rootMargin ?? config.defaultRootMargin,
|
|
327
383
|
repeat,
|
|
328
|
-
once:
|
|
329
|
-
offset:
|
|
330
|
-
stagger:
|
|
331
|
-
parallax:
|
|
332
|
-
onStart:
|
|
333
|
-
onComplete:
|
|
334
|
-
onEnter:
|
|
335
|
-
onLeave:
|
|
336
|
-
onProgress:
|
|
337
|
-
progressMode:
|
|
384
|
+
once: opts.once ?? (repeat ? false : config.defaultOnce),
|
|
385
|
+
offset: opts.offset ?? config.defaultOffset,
|
|
386
|
+
stagger: opts.stagger ?? 0,
|
|
387
|
+
parallax: opts.parallax ?? {},
|
|
388
|
+
onStart: opts.onStart ?? noop,
|
|
389
|
+
onComplete: opts.onComplete ?? noop,
|
|
390
|
+
onEnter: opts.onEnter ?? noop,
|
|
391
|
+
onLeave: opts.onLeave ?? noop,
|
|
392
|
+
onProgress: opts.onProgress ?? noop,
|
|
393
|
+
progressMode: opts.progressMode ?? 'ratio',
|
|
338
394
|
progressVar: opts.progressVar ? normalizeVar(opts.progressVar) : '',
|
|
395
|
+
engine,
|
|
396
|
+
viewRange: opts.viewRange ?? DEFAULT_VIEW_RANGE,
|
|
397
|
+
exit,
|
|
339
398
|
};
|
|
340
399
|
}
|
|
341
400
|
function resolveTargets(target) {
|
|
@@ -356,14 +415,13 @@ function resolveTargets(target) {
|
|
|
356
415
|
* @internal
|
|
357
416
|
*/
|
|
358
417
|
function applyOffset(rootMargin, offset) {
|
|
359
|
-
var _a, _b, _c;
|
|
360
418
|
if (!offset)
|
|
361
419
|
return rootMargin;
|
|
362
420
|
const parts = (rootMargin || '0px').trim().split(/\s+/);
|
|
363
421
|
const top = parts[0];
|
|
364
|
-
const right =
|
|
365
|
-
const bottom =
|
|
366
|
-
const left =
|
|
422
|
+
const right = parts[1] ?? top;
|
|
423
|
+
const bottom = parts[2] ?? top;
|
|
424
|
+
const left = parts[3] ?? right;
|
|
367
425
|
const m = /^(-?\d*\.?\d+)(px)?$/.exec(bottom);
|
|
368
426
|
const base = m ? parseFloat(m[1]) : 0; // non-px bottoms (e.g. %) cannot be combined; offset wins
|
|
369
427
|
return `${top} ${right} ${base - offset}px ${left}`;
|
|
@@ -401,8 +459,15 @@ function getScrollProgress(el, root) {
|
|
|
401
459
|
/* ------------------------------------------------------------------ */
|
|
402
460
|
/** The animation currently running on an element, so it can be cancelled/replaced. */
|
|
403
461
|
const running = new WeakMap();
|
|
462
|
+
/** Native engine: the scroll-linked exit animation, next to the entrance in `running`. */
|
|
463
|
+
const exits = new WeakMap();
|
|
404
464
|
const timers = new WeakMap();
|
|
405
465
|
function cancelRunning(el) {
|
|
466
|
+
const exitAnim = exits.get(el);
|
|
467
|
+
if (exitAnim) {
|
|
468
|
+
exits.delete(el);
|
|
469
|
+
exitAnim.cancel();
|
|
470
|
+
}
|
|
406
471
|
const anim = running.get(el);
|
|
407
472
|
if (anim) {
|
|
408
473
|
running.delete(el);
|
|
@@ -513,7 +578,7 @@ function stopAnimation(el, config = {}) {
|
|
|
513
578
|
function motionDisabled(config) {
|
|
514
579
|
return !!config.disabled || prefersReducedMotion();
|
|
515
580
|
}
|
|
516
|
-
function runAnimation(el, opts, config, staggerIndex = 0) {
|
|
581
|
+
function runAnimation(el, opts, config, staggerIndex = 0, pending) {
|
|
517
582
|
const { duration, delay, stagger, onStart, onComplete } = opts;
|
|
518
583
|
const totalDelay = Math.max(0, delay + staggerIndex * stagger);
|
|
519
584
|
cancelRunning(el);
|
|
@@ -529,8 +594,10 @@ function runAnimation(el, opts, config, staggerIndex = 0) {
|
|
|
529
594
|
el.classList.remove(config.hiddenClass);
|
|
530
595
|
el.classList.add(config.visibleClass);
|
|
531
596
|
onStart(el);
|
|
597
|
+
pending?.add(el);
|
|
532
598
|
timers.set(el, setTimeout(() => {
|
|
533
599
|
timers.delete(el);
|
|
600
|
+
pending?.delete(el);
|
|
534
601
|
onComplete(el);
|
|
535
602
|
}, duration + totalDelay));
|
|
536
603
|
return;
|
|
@@ -555,7 +622,7 @@ function runAnimation(el, opts, config, staggerIndex = 0) {
|
|
|
555
622
|
try {
|
|
556
623
|
anim = el.animate(built.keyframes, timing);
|
|
557
624
|
}
|
|
558
|
-
catch
|
|
625
|
+
catch {
|
|
559
626
|
// Invalid user easing string (WAAPI throws a TypeError): fall back to 'ease'
|
|
560
627
|
anim = el.animate(built.keyframes, { ...timing, easing: 'ease' });
|
|
561
628
|
}
|
|
@@ -570,7 +637,7 @@ function runAnimation(el, opts, config, staggerIndex = 0) {
|
|
|
570
637
|
try {
|
|
571
638
|
anim.commitStyles();
|
|
572
639
|
}
|
|
573
|
-
catch
|
|
640
|
+
catch {
|
|
574
641
|
setStyles(el, preset.to);
|
|
575
642
|
}
|
|
576
643
|
anim.cancel();
|
|
@@ -578,6 +645,69 @@ function runAnimation(el, opts, config, staggerIndex = 0) {
|
|
|
578
645
|
onComplete(el);
|
|
579
646
|
};
|
|
580
647
|
}
|
|
648
|
+
/**
|
|
649
|
+
* Native engine: attach the preset to a `ViewTimeline` of the element, so the
|
|
650
|
+
* browser drives it from scroll position. Returns `null` when that fails
|
|
651
|
+
* (caller falls back to the JS engine).
|
|
652
|
+
*/
|
|
653
|
+
function startNative(el, opts, onFrozen) {
|
|
654
|
+
if (typeof el.animate !== 'function')
|
|
655
|
+
return null;
|
|
656
|
+
const preset = resolvePreset(opts.animation);
|
|
657
|
+
const built = buildAnimation(preset, opts.easing);
|
|
658
|
+
let anim;
|
|
659
|
+
try {
|
|
660
|
+
const timeline = new globalThis.ViewTimeline({ subject: el, axis: 'block' });
|
|
661
|
+
const timing = {
|
|
662
|
+
fill: 'both',
|
|
663
|
+
easing: built.easing,
|
|
664
|
+
timeline,
|
|
665
|
+
rangeStart: opts.viewRange[0],
|
|
666
|
+
rangeEnd: opts.viewRange[1],
|
|
667
|
+
};
|
|
668
|
+
try {
|
|
669
|
+
anim = el.animate(built.keyframes, timing);
|
|
670
|
+
}
|
|
671
|
+
catch {
|
|
672
|
+
anim = el.animate(built.keyframes, { ...timing, easing: 'linear' });
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
catch {
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
running.set(el, anim);
|
|
679
|
+
if (opts.exit) {
|
|
680
|
+
// Reverse keyframes over the `exit` range; `fill: 'forwards'` so it has
|
|
681
|
+
// no effect before the element starts leaving.
|
|
682
|
+
const leave = resolvePreset(opts.exit === true ? opts.animation : opts.exit);
|
|
683
|
+
const out = buildAnimation({ from: leave.to, to: leave.from }, opts.easing);
|
|
684
|
+
try {
|
|
685
|
+
const timeline = new globalThis.ViewTimeline({ subject: el, axis: 'block' });
|
|
686
|
+
const timing = { fill: 'forwards', easing: out.easing, timeline, rangeStart: 'exit 0%', rangeEnd: 'exit 100%' };
|
|
687
|
+
exits.set(el, el.animate(out.keyframes, timing));
|
|
688
|
+
}
|
|
689
|
+
catch {
|
|
690
|
+
// Exit is cosmetic: ignore if unsupported.
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
const keep = opts.repeat || !opts.once;
|
|
694
|
+
anim.onfinish = () => {
|
|
695
|
+
// `once`: freeze the end state, so scrolling back up does not reverse it.
|
|
696
|
+
if (!keep && running.get(el) === anim) {
|
|
697
|
+
running.delete(el);
|
|
698
|
+
try {
|
|
699
|
+
anim.commitStyles();
|
|
700
|
+
}
|
|
701
|
+
catch {
|
|
702
|
+
setStyles(el, preset.to);
|
|
703
|
+
}
|
|
704
|
+
anim.cancel();
|
|
705
|
+
onFrozen?.();
|
|
706
|
+
}
|
|
707
|
+
opts.onComplete(el);
|
|
708
|
+
};
|
|
709
|
+
return anim;
|
|
710
|
+
}
|
|
581
711
|
function applyParallax(el, progress, parallax) {
|
|
582
712
|
const { x = 0, y = 0, rotate = 0, scale = 1, speed = 1 } = parallax;
|
|
583
713
|
const p = (progress - 0.5) * 2 * speed;
|
|
@@ -593,6 +723,26 @@ function applyParallax(el, progress, parallax) {
|
|
|
593
723
|
transform += ` scale(${1 + (scale - 1) * p})`;
|
|
594
724
|
el.style.transform = transform.trim();
|
|
595
725
|
}
|
|
726
|
+
/** Play the exit animation (entrance or `exit` preset, reversed), ending hidden. */
|
|
727
|
+
function runExit(el, opts, config) {
|
|
728
|
+
if (config.useClassNames || typeof el.animate !== 'function') {
|
|
729
|
+
hideElement(el, config);
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
cancelRunning(el);
|
|
733
|
+
const preset = resolvePreset(opts.exit === true ? opts.animation : opts.exit);
|
|
734
|
+
const built = buildAnimation({ from: preset.to, to: preset.from }, opts.easing);
|
|
735
|
+
const timing = { duration: opts.duration, easing: built.easing, fill: 'forwards' };
|
|
736
|
+
let anim;
|
|
737
|
+
try {
|
|
738
|
+
anim = el.animate(built.keyframes, timing);
|
|
739
|
+
}
|
|
740
|
+
catch {
|
|
741
|
+
anim = el.animate(built.keyframes, { ...timing, easing: 'ease' });
|
|
742
|
+
}
|
|
743
|
+
// Kept (filling) until the next entrance cancels it.
|
|
744
|
+
running.set(el, anim);
|
|
745
|
+
}
|
|
596
746
|
function hideElement(el, config) {
|
|
597
747
|
cancelRunning(el);
|
|
598
748
|
if (config.useClassNames) {
|
|
@@ -635,6 +785,10 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
635
785
|
let listening = null;
|
|
636
786
|
// Active watch() MutationObservers, disconnected by destroy().
|
|
637
787
|
const watchers = new Set();
|
|
788
|
+
// Elements with a pending class-name completion timer started by this instance.
|
|
789
|
+
const pending = new Set();
|
|
790
|
+
// Elements whose entrance runs on a native scroll-driven timeline.
|
|
791
|
+
const natives = new Set();
|
|
638
792
|
// Observers are shared between elements with the same root/threshold/rootMargin,
|
|
639
793
|
// instead of one (or two) IntersectionObservers per element.
|
|
640
794
|
const pools = new Map();
|
|
@@ -648,14 +802,27 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
648
802
|
return io;
|
|
649
803
|
}
|
|
650
804
|
function teardown(el, restore) {
|
|
651
|
-
var _a;
|
|
652
805
|
const record = registry.get(el);
|
|
653
806
|
if (!record)
|
|
654
807
|
return;
|
|
655
808
|
record.observer.unobserve(el);
|
|
656
|
-
|
|
809
|
+
record.progressObserver?.unobserve(el);
|
|
657
810
|
registry.delete(el);
|
|
658
811
|
untrack(el);
|
|
812
|
+
if (record.engine === 'css') {
|
|
813
|
+
// A scroll-linked animation would keep following the scroll: stop it and
|
|
814
|
+
// show the element (unless it finished and is just being released).
|
|
815
|
+
if (restore) {
|
|
816
|
+
natives.delete(el);
|
|
817
|
+
reveal(el, config);
|
|
818
|
+
}
|
|
819
|
+
else if (el.isConnected === false) {
|
|
820
|
+
// Left the DOM: drop it too, or the instance keeps it alive (and
|
|
821
|
+
// destroy() touches it) for its whole lifetime.
|
|
822
|
+
natives.delete(el);
|
|
823
|
+
}
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
659
826
|
// An element that never animated would otherwise stay invisible forever.
|
|
660
827
|
if (restore && !record.animated)
|
|
661
828
|
reveal(el, config);
|
|
@@ -724,7 +891,6 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
724
891
|
function onIntersect(entries) {
|
|
725
892
|
const staggerCounts = new Map();
|
|
726
893
|
entries.forEach((entry) => {
|
|
727
|
-
var _a;
|
|
728
894
|
const el = entry.target;
|
|
729
895
|
const record = registry.get(el);
|
|
730
896
|
if (!record)
|
|
@@ -734,6 +900,29 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
734
900
|
teardown(el, false);
|
|
735
901
|
return;
|
|
736
902
|
}
|
|
903
|
+
if (record.engine === 'css') {
|
|
904
|
+
// The browser drives the animation; only lifecycle bookkeeping here.
|
|
905
|
+
if (entry.isIntersecting) {
|
|
906
|
+
opts.onEnter(el);
|
|
907
|
+
if (!record.animated) {
|
|
908
|
+
record.animated = true;
|
|
909
|
+
opts.onStart(el);
|
|
910
|
+
}
|
|
911
|
+
if (opts.once && !opts.repeat) {
|
|
912
|
+
record.observer.unobserve(el);
|
|
913
|
+
if (config.autoUnregister && !record.progressObserver) {
|
|
914
|
+
finished.add(el);
|
|
915
|
+
teardown(el, false);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
else {
|
|
920
|
+
opts.onLeave(el);
|
|
921
|
+
if (opts.repeat)
|
|
922
|
+
record.animated = false;
|
|
923
|
+
}
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
737
926
|
if (entry.isIntersecting) {
|
|
738
927
|
opts.onEnter(el);
|
|
739
928
|
if (record.animated && !opts.repeat)
|
|
@@ -743,10 +932,10 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
743
932
|
let staggerIndex = 0;
|
|
744
933
|
if (opts.stagger > 0) {
|
|
745
934
|
const parent = el.parentElement;
|
|
746
|
-
staggerIndex =
|
|
935
|
+
staggerIndex = staggerCounts.get(parent) ?? 0;
|
|
747
936
|
staggerCounts.set(parent, staggerIndex + 1);
|
|
748
937
|
}
|
|
749
|
-
runAnimation(el, opts, config, staggerIndex);
|
|
938
|
+
runAnimation(el, opts, config, staggerIndex, pending);
|
|
750
939
|
record.animated = true;
|
|
751
940
|
if (opts.once && !opts.repeat) {
|
|
752
941
|
// Keep the progress observer: parallax/onProgress must keep working.
|
|
@@ -762,8 +951,12 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
762
951
|
else {
|
|
763
952
|
opts.onLeave(el);
|
|
764
953
|
if (opts.repeat && record.animated) {
|
|
765
|
-
if (!motionDisabled(config))
|
|
766
|
-
|
|
954
|
+
if (!motionDisabled(config)) {
|
|
955
|
+
if (opts.exit)
|
|
956
|
+
runExit(el, opts, config);
|
|
957
|
+
else
|
|
958
|
+
hideElement(el, config);
|
|
959
|
+
}
|
|
767
960
|
record.animated = false;
|
|
768
961
|
}
|
|
769
962
|
}
|
|
@@ -797,12 +990,14 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
797
990
|
}));
|
|
798
991
|
}
|
|
799
992
|
function attach(el, record, observeMain) {
|
|
800
|
-
var _a;
|
|
801
993
|
record.observer = getObserver(record.options);
|
|
802
994
|
if (observeMain)
|
|
803
995
|
record.observer.observe(el);
|
|
804
996
|
record.progressObserver = needsProgress(record.options) ? getProgressObserver(record.options) : undefined;
|
|
805
|
-
|
|
997
|
+
record.progressObserver?.observe(el);
|
|
998
|
+
}
|
|
999
|
+
function wantsNative(opts) {
|
|
1000
|
+
return opts.engine !== 'js' && !config.useClassNames && !motionDisabled(config) && supportsScrollTimeline();
|
|
806
1001
|
}
|
|
807
1002
|
function observeElement(el, opts) {
|
|
808
1003
|
if (registry.has(el) || finished.has(el))
|
|
@@ -812,9 +1007,16 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
812
1007
|
reveal(el, config);
|
|
813
1008
|
return;
|
|
814
1009
|
}
|
|
815
|
-
|
|
1010
|
+
const record = { element: el, options: opts, animated: false, engine: 'js' };
|
|
1011
|
+
if (wantsNative(opts)) {
|
|
1012
|
+
cancelRunning(el);
|
|
1013
|
+
if (startNative(el, opts, () => natives.delete(el))) {
|
|
1014
|
+
record.engine = 'css';
|
|
1015
|
+
natives.add(el);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
if (record.engine === 'js' && !motionDisabled(config))
|
|
816
1019
|
hideElement(el, config);
|
|
817
|
-
const record = { element: el, options: opts, animated: false };
|
|
818
1020
|
registry.set(el, record);
|
|
819
1021
|
attach(el, record, true);
|
|
820
1022
|
}
|
|
@@ -835,14 +1037,14 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
835
1037
|
});
|
|
836
1038
|
},
|
|
837
1039
|
init(rootElement) {
|
|
838
|
-
const scope = rootElement
|
|
1040
|
+
const scope = rootElement ?? (hasDOM() ? document : null);
|
|
839
1041
|
if (!scope)
|
|
840
1042
|
return;
|
|
841
1043
|
pruneDetached();
|
|
842
1044
|
scope.querySelectorAll('[data-sa]').forEach(observeDataElement);
|
|
843
1045
|
},
|
|
844
1046
|
watch(rootElement) {
|
|
845
|
-
const scope = rootElement
|
|
1047
|
+
const scope = rootElement ?? (hasDOM() ? document : null);
|
|
846
1048
|
if (!scope || typeof MutationObserver === 'undefined')
|
|
847
1049
|
return noop;
|
|
848
1050
|
instance.init(scope);
|
|
@@ -881,10 +1083,25 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
881
1083
|
destroy() {
|
|
882
1084
|
watchers.forEach((mo) => mo.disconnect());
|
|
883
1085
|
watchers.clear();
|
|
1086
|
+
// Class-name mode: drop completion timers so onComplete never fires after destroy().
|
|
1087
|
+
pending.forEach((el) => {
|
|
1088
|
+
const timer = timers.get(el);
|
|
1089
|
+
if (timer !== undefined) {
|
|
1090
|
+
clearTimeout(timer);
|
|
1091
|
+
timers.delete(el);
|
|
1092
|
+
}
|
|
1093
|
+
});
|
|
1094
|
+
pending.clear();
|
|
884
1095
|
registry.forEach((record, el) => {
|
|
885
|
-
if (!record.animated)
|
|
1096
|
+
if (!record.animated && record.engine !== 'css')
|
|
1097
|
+
reveal(el, config);
|
|
1098
|
+
});
|
|
1099
|
+
// Native scroll-linked animations still running: stop them, show content.
|
|
1100
|
+
natives.forEach((el) => {
|
|
1101
|
+
if (running.has(el))
|
|
886
1102
|
reveal(el, config);
|
|
887
1103
|
});
|
|
1104
|
+
natives.clear();
|
|
888
1105
|
pools.forEach((pool) => pool.forEach((io) => io.disconnect()));
|
|
889
1106
|
pools.clear();
|
|
890
1107
|
registry.clear();
|
|
@@ -909,7 +1126,7 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
909
1126
|
},
|
|
910
1127
|
animate(target, options = {}) {
|
|
911
1128
|
const opts = mergeOptions(options, config);
|
|
912
|
-
resolveTargets(target).forEach((el) => runAnimation(el, opts, config, 0));
|
|
1129
|
+
resolveTargets(target).forEach((el) => runAnimation(el, opts, config, 0, pending));
|
|
913
1130
|
},
|
|
914
1131
|
getObservedElements() {
|
|
915
1132
|
return Array.from(registry.values());
|
|
@@ -921,329 +1138,5 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
921
1138
|
return instance;
|
|
922
1139
|
}
|
|
923
1140
|
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
* Reveal a container's children one after another when the container scrolls
|
|
927
|
-
* into view, optionally also animating children that are added later.
|
|
928
|
-
*/
|
|
929
|
-
let fallback$1 = null;
|
|
930
|
-
/**
|
|
931
|
-
* Animate the children of `container` with a stagger once it enters the
|
|
932
|
-
* viewport. Returns a cleanup function. SSR-safe (no-op without a DOM).
|
|
933
|
-
*
|
|
934
|
-
* @example
|
|
935
|
-
* const stop = staggerChildren(document.querySelector('ul'), { stagger: 60, observeChildren: true });
|
|
936
|
-
*/
|
|
937
|
-
function staggerChildren(container, options = {}, instance) {
|
|
938
|
-
if (!container || !hasDOM() || !supportsObserver())
|
|
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.esm.js.map
|
|
1141
|
+
export { EASING_MAP as E, PRESETS as P, stopAnimation as a, prefersReducedMotion as b, createScrollAnimate as c, resolveEasing as d, resolvePreset as e, supportsScrollTimeline as f, getScrollProgress as g, hasDOM as h, readOptions as i, prepareElement as p, resolveTargets as r, supportsObserver as s };
|
|
1142
|
+
//# sourceMappingURL=core-BP-a1iNc.js.map
|