use-scroll-animate 1.4.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +68 -0
- package/README.md +184 -10
- package/README_ja.md +57 -1
- package/README_zh.md +68 -1
- package/dist/{index.mjs → chunks/core-CH41ekIo.cjs} +366 -393
- package/dist/chunks/core-CH41ekIo.cjs.map +1 -0
- package/dist/{index.esm.js → chunks/core-FUEi4ncH.js} +351 -393
- package/dist/chunks/core-FUEi4ncH.js.map +1 -0
- package/dist/chunks/stagger-DabrnrcE.js +84 -0
- package/dist/chunks/stagger-DabrnrcE.js.map +1 -0
- package/dist/chunks/stagger-XD-0-FQz.cjs +86 -0
- package/dist/chunks/stagger-XD-0-FQz.cjs.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} +87 -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} +98 -51
- package/dist/index.d.ts +98 -51
- package/dist/index.js +105 -1070
- 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
|
/* ------------------------------------------------------------------ */
|
|
@@ -252,23 +270,37 @@ function lengthValue(value) {
|
|
|
252
270
|
const v = value.trim();
|
|
253
271
|
return /^-?(\d+\.?\d*|\.\d+)$/.test(v) ? parseFloat(v) : v;
|
|
254
272
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
273
|
+
const DEFAULT_PROGRESS_VAR = '--sa-progress';
|
|
274
|
+
const DEFAULT_VIEW_RANGE = ['entry 0%', 'entry 100%'];
|
|
275
|
+
/** `''` (bare attribute) -> default name; `sa-progress` -> `--sa-progress`. */
|
|
276
|
+
function normalizeVar(name) {
|
|
277
|
+
const v = name.trim();
|
|
278
|
+
if (!v)
|
|
279
|
+
return DEFAULT_PROGRESS_VAR;
|
|
280
|
+
return v.startsWith('--') ? v : `--${v}`;
|
|
281
|
+
}
|
|
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) {
|
|
258
289
|
const opts = {};
|
|
259
|
-
|
|
260
|
-
|
|
290
|
+
const anim = get('animation');
|
|
291
|
+
if (anim) {
|
|
261
292
|
opts.animation = (anim.includes(',') ? anim.split(',').map((s) => s.trim()) : anim.trim());
|
|
262
293
|
}
|
|
263
|
-
opts.duration = num(
|
|
264
|
-
opts.delay = num(
|
|
265
|
-
|
|
266
|
-
|
|
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();
|
|
267
299
|
if (e.startsWith('[')) {
|
|
268
300
|
try {
|
|
269
301
|
opts.easing = JSON.parse(e);
|
|
270
302
|
}
|
|
271
|
-
catch
|
|
303
|
+
catch {
|
|
272
304
|
// Malformed JSON: fall back to the default easing instead of throwing
|
|
273
305
|
}
|
|
274
306
|
}
|
|
@@ -276,55 +308,95 @@ function parseDataAttributes(el, config) {
|
|
|
276
308
|
opts.easing = e;
|
|
277
309
|
}
|
|
278
310
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
opts.threshold = t.includes(',')
|
|
282
|
-
|
|
283
|
-
|
|
311
|
+
const t = get('threshold');
|
|
312
|
+
if (t) {
|
|
313
|
+
opts.threshold = t.includes(',') ? t.split(',').map(parseFloat).filter(Number.isFinite) : num(t);
|
|
314
|
+
}
|
|
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);
|
|
284
342
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (dataset.saProgress)
|
|
294
|
-
opts.progressMode = dataset.saProgress.trim() === 'scroll' ? 'scroll' : 'ratio';
|
|
295
|
-
if (dataset.saParallaxX || dataset.saParallaxY || dataset.saParallaxRotate || dataset.saParallaxScale) {
|
|
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) {
|
|
296
351
|
opts.parallax = {
|
|
297
|
-
x: lengthValue(
|
|
298
|
-
y: lengthValue(
|
|
299
|
-
rotate: num(
|
|
300
|
-
scale: num(
|
|
301
|
-
speed:
|
|
352
|
+
x: lengthValue(px),
|
|
353
|
+
y: lengthValue(py),
|
|
354
|
+
rotate: num(pr),
|
|
355
|
+
scale: num(ps),
|
|
356
|
+
speed: num(get('parallax-speed')) ?? 1,
|
|
302
357
|
};
|
|
303
358
|
}
|
|
304
|
-
|
|
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);
|
|
305
368
|
}
|
|
306
369
|
function mergeOptions(opts, config) {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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;
|
|
310
378
|
return {
|
|
311
|
-
animation:
|
|
312
|
-
duration:
|
|
313
|
-
delay:
|
|
314
|
-
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,
|
|
315
383
|
threshold: Array.isArray(threshold) && threshold.length === 0 ? config.defaultThreshold : threshold,
|
|
316
|
-
rootMargin:
|
|
384
|
+
rootMargin: opts.rootMargin ?? config.defaultRootMargin,
|
|
317
385
|
repeat,
|
|
318
|
-
once:
|
|
319
|
-
offset:
|
|
320
|
-
stagger:
|
|
321
|
-
parallax:
|
|
322
|
-
onStart:
|
|
323
|
-
onComplete:
|
|
324
|
-
onEnter:
|
|
325
|
-
onLeave:
|
|
326
|
-
onProgress:
|
|
327
|
-
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',
|
|
396
|
+
progressVar: opts.progressVar ? normalizeVar(opts.progressVar) : '',
|
|
397
|
+
engine,
|
|
398
|
+
viewRange: opts.viewRange ?? DEFAULT_VIEW_RANGE,
|
|
399
|
+
exit,
|
|
328
400
|
};
|
|
329
401
|
}
|
|
330
402
|
function resolveTargets(target) {
|
|
@@ -345,14 +417,13 @@ function resolveTargets(target) {
|
|
|
345
417
|
* @internal
|
|
346
418
|
*/
|
|
347
419
|
function applyOffset(rootMargin, offset) {
|
|
348
|
-
var _a, _b, _c;
|
|
349
420
|
if (!offset)
|
|
350
421
|
return rootMargin;
|
|
351
422
|
const parts = (rootMargin || '0px').trim().split(/\s+/);
|
|
352
423
|
const top = parts[0];
|
|
353
|
-
const right =
|
|
354
|
-
const bottom =
|
|
355
|
-
const left =
|
|
424
|
+
const right = parts[1] ?? top;
|
|
425
|
+
const bottom = parts[2] ?? top;
|
|
426
|
+
const left = parts[3] ?? right;
|
|
356
427
|
const m = /^(-?\d*\.?\d+)(px)?$/.exec(bottom);
|
|
357
428
|
const base = m ? parseFloat(m[1]) : 0; // non-px bottoms (e.g. %) cannot be combined; offset wins
|
|
358
429
|
return `${top} ${right} ${base - offset}px ${left}`;
|
|
@@ -361,7 +432,7 @@ function hasParallax(p) {
|
|
|
361
432
|
return !!p && Object.keys(p).some((k) => p[k] !== undefined);
|
|
362
433
|
}
|
|
363
434
|
function needsProgress(opts) {
|
|
364
|
-
return hasParallax(opts.parallax) || opts.onProgress !== noop;
|
|
435
|
+
return hasParallax(opts.parallax) || opts.onProgress !== noop || !!opts.progressVar;
|
|
365
436
|
}
|
|
366
437
|
/**
|
|
367
438
|
* True scroll progress of `el` through the viewport (or `root`): 0 when its top
|
|
@@ -390,8 +461,15 @@ function getScrollProgress(el, root) {
|
|
|
390
461
|
/* ------------------------------------------------------------------ */
|
|
391
462
|
/** The animation currently running on an element, so it can be cancelled/replaced. */
|
|
392
463
|
const running = new WeakMap();
|
|
464
|
+
/** Native engine: the scroll-linked exit animation, next to the entrance in `running`. */
|
|
465
|
+
const exits = new WeakMap();
|
|
393
466
|
const timers = new WeakMap();
|
|
394
467
|
function cancelRunning(el) {
|
|
468
|
+
const exitAnim = exits.get(el);
|
|
469
|
+
if (exitAnim) {
|
|
470
|
+
exits.delete(el);
|
|
471
|
+
exitAnim.cancel();
|
|
472
|
+
}
|
|
395
473
|
const anim = running.get(el);
|
|
396
474
|
if (anim) {
|
|
397
475
|
running.delete(el);
|
|
@@ -502,7 +580,7 @@ function stopAnimation(el, config = {}) {
|
|
|
502
580
|
function motionDisabled(config) {
|
|
503
581
|
return !!config.disabled || prefersReducedMotion();
|
|
504
582
|
}
|
|
505
|
-
function runAnimation(el, opts, config, staggerIndex = 0) {
|
|
583
|
+
function runAnimation(el, opts, config, staggerIndex = 0, pending) {
|
|
506
584
|
const { duration, delay, stagger, onStart, onComplete } = opts;
|
|
507
585
|
const totalDelay = Math.max(0, delay + staggerIndex * stagger);
|
|
508
586
|
cancelRunning(el);
|
|
@@ -518,8 +596,10 @@ function runAnimation(el, opts, config, staggerIndex = 0) {
|
|
|
518
596
|
el.classList.remove(config.hiddenClass);
|
|
519
597
|
el.classList.add(config.visibleClass);
|
|
520
598
|
onStart(el);
|
|
599
|
+
pending?.add(el);
|
|
521
600
|
timers.set(el, setTimeout(() => {
|
|
522
601
|
timers.delete(el);
|
|
602
|
+
pending?.delete(el);
|
|
523
603
|
onComplete(el);
|
|
524
604
|
}, duration + totalDelay));
|
|
525
605
|
return;
|
|
@@ -544,7 +624,7 @@ function runAnimation(el, opts, config, staggerIndex = 0) {
|
|
|
544
624
|
try {
|
|
545
625
|
anim = el.animate(built.keyframes, timing);
|
|
546
626
|
}
|
|
547
|
-
catch
|
|
627
|
+
catch {
|
|
548
628
|
// Invalid user easing string (WAAPI throws a TypeError): fall back to 'ease'
|
|
549
629
|
anim = el.animate(built.keyframes, { ...timing, easing: 'ease' });
|
|
550
630
|
}
|
|
@@ -559,7 +639,7 @@ function runAnimation(el, opts, config, staggerIndex = 0) {
|
|
|
559
639
|
try {
|
|
560
640
|
anim.commitStyles();
|
|
561
641
|
}
|
|
562
|
-
catch
|
|
642
|
+
catch {
|
|
563
643
|
setStyles(el, preset.to);
|
|
564
644
|
}
|
|
565
645
|
anim.cancel();
|
|
@@ -567,6 +647,69 @@ function runAnimation(el, opts, config, staggerIndex = 0) {
|
|
|
567
647
|
onComplete(el);
|
|
568
648
|
};
|
|
569
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
|
+
}
|
|
570
713
|
function applyParallax(el, progress, parallax) {
|
|
571
714
|
const { x = 0, y = 0, rotate = 0, scale = 1, speed = 1 } = parallax;
|
|
572
715
|
const p = (progress - 0.5) * 2 * speed;
|
|
@@ -582,6 +725,26 @@ function applyParallax(el, progress, parallax) {
|
|
|
582
725
|
transform += ` scale(${1 + (scale - 1) * p})`;
|
|
583
726
|
el.style.transform = transform.trim();
|
|
584
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
|
+
}
|
|
585
748
|
function hideElement(el, config) {
|
|
586
749
|
cancelRunning(el);
|
|
587
750
|
if (config.useClassNames) {
|
|
@@ -622,6 +785,12 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
622
785
|
const scrolling = new Set();
|
|
623
786
|
let frame = 0;
|
|
624
787
|
let listening = null;
|
|
788
|
+
// Active watch() MutationObservers, disconnected by destroy().
|
|
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();
|
|
625
794
|
// Observers are shared between elements with the same root/threshold/rootMargin,
|
|
626
795
|
// instead of one (or two) IntersectionObservers per element.
|
|
627
796
|
const pools = new Map();
|
|
@@ -635,14 +804,22 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
635
804
|
return io;
|
|
636
805
|
}
|
|
637
806
|
function teardown(el, restore) {
|
|
638
|
-
var _a;
|
|
639
807
|
const record = registry.get(el);
|
|
640
808
|
if (!record)
|
|
641
809
|
return;
|
|
642
810
|
record.observer.unobserve(el);
|
|
643
|
-
|
|
811
|
+
record.progressObserver?.unobserve(el);
|
|
644
812
|
registry.delete(el);
|
|
645
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
|
+
return;
|
|
822
|
+
}
|
|
646
823
|
// An element that never animated would otherwise stay invisible forever.
|
|
647
824
|
if (restore && !record.animated)
|
|
648
825
|
reveal(el, config);
|
|
@@ -656,6 +833,11 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
656
833
|
function emitProgress(el, record, progress) {
|
|
657
834
|
const opts = record.options;
|
|
658
835
|
opts.onProgress(el, progress);
|
|
836
|
+
if (opts.progressVar) {
|
|
837
|
+
const style = el.style;
|
|
838
|
+
if (style)
|
|
839
|
+
style.setProperty(opts.progressVar, String(+progress.toFixed(4)));
|
|
840
|
+
}
|
|
659
841
|
if (hasParallax(opts.parallax) && !motionDisabled(config))
|
|
660
842
|
applyParallax(el, progress, opts.parallax);
|
|
661
843
|
}
|
|
@@ -706,7 +888,6 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
706
888
|
function onIntersect(entries) {
|
|
707
889
|
const staggerCounts = new Map();
|
|
708
890
|
entries.forEach((entry) => {
|
|
709
|
-
var _a;
|
|
710
891
|
const el = entry.target;
|
|
711
892
|
const record = registry.get(el);
|
|
712
893
|
if (!record)
|
|
@@ -716,6 +897,29 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
716
897
|
teardown(el, false);
|
|
717
898
|
return;
|
|
718
899
|
}
|
|
900
|
+
if (record.engine === 'css') {
|
|
901
|
+
// The browser drives the animation; only lifecycle bookkeeping here.
|
|
902
|
+
if (entry.isIntersecting) {
|
|
903
|
+
opts.onEnter(el);
|
|
904
|
+
if (!record.animated) {
|
|
905
|
+
record.animated = true;
|
|
906
|
+
opts.onStart(el);
|
|
907
|
+
}
|
|
908
|
+
if (opts.once && !opts.repeat) {
|
|
909
|
+
record.observer.unobserve(el);
|
|
910
|
+
if (config.autoUnregister && !record.progressObserver) {
|
|
911
|
+
finished.add(el);
|
|
912
|
+
teardown(el, false);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
else {
|
|
917
|
+
opts.onLeave(el);
|
|
918
|
+
if (opts.repeat)
|
|
919
|
+
record.animated = false;
|
|
920
|
+
}
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
719
923
|
if (entry.isIntersecting) {
|
|
720
924
|
opts.onEnter(el);
|
|
721
925
|
if (record.animated && !opts.repeat)
|
|
@@ -725,10 +929,10 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
725
929
|
let staggerIndex = 0;
|
|
726
930
|
if (opts.stagger > 0) {
|
|
727
931
|
const parent = el.parentElement;
|
|
728
|
-
staggerIndex =
|
|
932
|
+
staggerIndex = staggerCounts.get(parent) ?? 0;
|
|
729
933
|
staggerCounts.set(parent, staggerIndex + 1);
|
|
730
934
|
}
|
|
731
|
-
runAnimation(el, opts, config, staggerIndex);
|
|
935
|
+
runAnimation(el, opts, config, staggerIndex, pending);
|
|
732
936
|
record.animated = true;
|
|
733
937
|
if (opts.once && !opts.repeat) {
|
|
734
938
|
// Keep the progress observer: parallax/onProgress must keep working.
|
|
@@ -744,8 +948,12 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
744
948
|
else {
|
|
745
949
|
opts.onLeave(el);
|
|
746
950
|
if (opts.repeat && record.animated) {
|
|
747
|
-
if (!motionDisabled(config))
|
|
748
|
-
|
|
951
|
+
if (!motionDisabled(config)) {
|
|
952
|
+
if (opts.exit)
|
|
953
|
+
runExit(el, opts, config);
|
|
954
|
+
else
|
|
955
|
+
hideElement(el, config);
|
|
956
|
+
}
|
|
749
957
|
record.animated = false;
|
|
750
958
|
}
|
|
751
959
|
}
|
|
@@ -779,12 +987,14 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
779
987
|
}));
|
|
780
988
|
}
|
|
781
989
|
function attach(el, record, observeMain) {
|
|
782
|
-
var _a;
|
|
783
990
|
record.observer = getObserver(record.options);
|
|
784
991
|
if (observeMain)
|
|
785
992
|
record.observer.observe(el);
|
|
786
993
|
record.progressObserver = needsProgress(record.options) ? getProgressObserver(record.options) : undefined;
|
|
787
|
-
|
|
994
|
+
record.progressObserver?.observe(el);
|
|
995
|
+
}
|
|
996
|
+
function wantsNative(opts) {
|
|
997
|
+
return opts.engine !== 'js' && !config.useClassNames && !motionDisabled(config) && supportsScrollTimeline();
|
|
788
998
|
}
|
|
789
999
|
function observeElement(el, opts) {
|
|
790
1000
|
if (registry.has(el) || finished.has(el))
|
|
@@ -794,12 +1004,23 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
794
1004
|
reveal(el, config);
|
|
795
1005
|
return;
|
|
796
1006
|
}
|
|
797
|
-
|
|
1007
|
+
const record = { element: el, options: opts, animated: false, engine: 'js' };
|
|
1008
|
+
if (wantsNative(opts)) {
|
|
1009
|
+
cancelRunning(el);
|
|
1010
|
+
if (startNative(el, opts, () => natives.delete(el))) {
|
|
1011
|
+
record.engine = 'css';
|
|
1012
|
+
natives.add(el);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
if (record.engine === 'js' && !motionDisabled(config))
|
|
798
1016
|
hideElement(el, config);
|
|
799
|
-
const record = { element: el, options: opts, animated: false };
|
|
800
1017
|
registry.set(el, record);
|
|
801
1018
|
attach(el, record, true);
|
|
802
1019
|
}
|
|
1020
|
+
function observeDataElement(el) {
|
|
1021
|
+
if (!registry.has(el))
|
|
1022
|
+
observeElement(el, parseDataAttributes(el, config));
|
|
1023
|
+
}
|
|
803
1024
|
const instance = {
|
|
804
1025
|
observe(target, options = {}) {
|
|
805
1026
|
pruneDetached();
|
|
@@ -813,20 +1034,71 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
813
1034
|
});
|
|
814
1035
|
},
|
|
815
1036
|
init(rootElement) {
|
|
816
|
-
const scope = rootElement
|
|
1037
|
+
const scope = rootElement ?? (hasDOM() ? document : null);
|
|
817
1038
|
if (!scope)
|
|
818
1039
|
return;
|
|
819
1040
|
pruneDetached();
|
|
820
|
-
scope.querySelectorAll('[data-sa]').forEach(
|
|
821
|
-
|
|
822
|
-
|
|
1041
|
+
scope.querySelectorAll('[data-sa]').forEach(observeDataElement);
|
|
1042
|
+
},
|
|
1043
|
+
watch(rootElement) {
|
|
1044
|
+
const scope = rootElement ?? (hasDOM() ? document : null);
|
|
1045
|
+
if (!scope || typeof MutationObserver === 'undefined')
|
|
1046
|
+
return noop;
|
|
1047
|
+
instance.init(scope);
|
|
1048
|
+
const observeTree = (node) => {
|
|
1049
|
+
if (node.hasAttribute('data-sa'))
|
|
1050
|
+
observeDataElement(node);
|
|
1051
|
+
node.querySelectorAll('[data-sa]').forEach(observeDataElement);
|
|
1052
|
+
};
|
|
1053
|
+
const mo = new MutationObserver((records) => {
|
|
1054
|
+
let removed = false;
|
|
1055
|
+
records.forEach((record) => {
|
|
1056
|
+
if (record.type === 'attributes') {
|
|
1057
|
+
const target = record.target;
|
|
1058
|
+
if (target.isConnected !== false)
|
|
1059
|
+
observeTree(target);
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
record.addedNodes.forEach((node) => {
|
|
1063
|
+
if (node instanceof Element && node.isConnected !== false)
|
|
1064
|
+
observeTree(node);
|
|
1065
|
+
});
|
|
1066
|
+
if (record.removedNodes.length)
|
|
1067
|
+
removed = true;
|
|
1068
|
+
});
|
|
1069
|
+
// Free elements that left the DOM (they can't animate any more).
|
|
1070
|
+
if (removed)
|
|
1071
|
+
pruneDetached();
|
|
823
1072
|
});
|
|
1073
|
+
mo.observe(scope, { childList: true, subtree: true, attributes: true, attributeFilter: ['data-sa'] });
|
|
1074
|
+
watchers.add(mo);
|
|
1075
|
+
return () => {
|
|
1076
|
+
mo.disconnect();
|
|
1077
|
+
watchers.delete(mo);
|
|
1078
|
+
};
|
|
824
1079
|
},
|
|
825
1080
|
destroy() {
|
|
1081
|
+
watchers.forEach((mo) => mo.disconnect());
|
|
1082
|
+
watchers.clear();
|
|
1083
|
+
// Class-name mode: drop completion timers so onComplete never fires after destroy().
|
|
1084
|
+
pending.forEach((el) => {
|
|
1085
|
+
const timer = timers.get(el);
|
|
1086
|
+
if (timer !== undefined) {
|
|
1087
|
+
clearTimeout(timer);
|
|
1088
|
+
timers.delete(el);
|
|
1089
|
+
}
|
|
1090
|
+
});
|
|
1091
|
+
pending.clear();
|
|
826
1092
|
registry.forEach((record, el) => {
|
|
827
|
-
if (!record.animated)
|
|
1093
|
+
if (!record.animated && record.engine !== 'css')
|
|
1094
|
+
reveal(el, config);
|
|
1095
|
+
});
|
|
1096
|
+
// Native scroll-linked animations still running: stop them, show content.
|
|
1097
|
+
natives.forEach((el) => {
|
|
1098
|
+
if (running.has(el))
|
|
828
1099
|
reveal(el, config);
|
|
829
1100
|
});
|
|
1101
|
+
natives.clear();
|
|
830
1102
|
pools.forEach((pool) => pool.forEach((io) => io.disconnect()));
|
|
831
1103
|
pools.clear();
|
|
832
1104
|
registry.clear();
|
|
@@ -851,7 +1123,7 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
851
1123
|
},
|
|
852
1124
|
animate(target, options = {}) {
|
|
853
1125
|
const opts = mergeOptions(options, config);
|
|
854
|
-
resolveTargets(target).forEach((el) => runAnimation(el, opts, config, 0));
|
|
1126
|
+
resolveTargets(target).forEach((el) => runAnimation(el, opts, config, 0, pending));
|
|
855
1127
|
},
|
|
856
1128
|
getObservedElements() {
|
|
857
1129
|
return Array.from(registry.values());
|
|
@@ -863,317 +1135,18 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
863
1135
|
return instance;
|
|
864
1136
|
}
|
|
865
1137
|
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
return () => undefined; // leave content visible
|
|
882
|
-
const sa = instance || fallback$1 || (fallback$1 = createScrollAnimate());
|
|
883
|
-
const { stagger = 80, delay = 0, threshold = 0.1, rootMargin = '0px', observeChildren = false, ...rest } = options;
|
|
884
|
-
let items = Array.from(container.children);
|
|
885
|
-
let revealed = false;
|
|
886
|
-
const late = [];
|
|
887
|
-
items.forEach((child) => prepareElement(child));
|
|
888
|
-
const io = new IntersectionObserver((entries) => {
|
|
889
|
-
if (revealed || !entries.some((entry) => entry.isIntersecting))
|
|
890
|
-
return;
|
|
891
|
-
revealed = true;
|
|
892
|
-
io.disconnect();
|
|
893
|
-
items.forEach((child, i) => {
|
|
894
|
-
if (child.parentNode === container)
|
|
895
|
-
sa.animate(child, { ...rest, delay: delay + i * stagger });
|
|
896
|
-
});
|
|
897
|
-
items = [];
|
|
898
|
-
}, { threshold, rootMargin });
|
|
899
|
-
io.observe(container);
|
|
900
|
-
let mo;
|
|
901
|
-
if (observeChildren && typeof MutationObserver !== 'undefined') {
|
|
902
|
-
mo = new MutationObserver((records) => {
|
|
903
|
-
records.forEach((record) => {
|
|
904
|
-
record.addedNodes.forEach((node) => {
|
|
905
|
-
if (!(node instanceof Element) || node.parentNode !== container)
|
|
906
|
-
return;
|
|
907
|
-
if (!revealed) {
|
|
908
|
-
prepareElement(node);
|
|
909
|
-
items.push(node);
|
|
910
|
-
}
|
|
911
|
-
else {
|
|
912
|
-
// The core engine staggers siblings relative to the batch that
|
|
913
|
-
// enters the viewport together.
|
|
914
|
-
late.push(node);
|
|
915
|
-
sa.observe(node, { ...rest, delay, stagger, threshold, rootMargin });
|
|
916
|
-
}
|
|
917
|
-
});
|
|
918
|
-
record.removedNodes.forEach((node) => {
|
|
919
|
-
if (!(node instanceof Element))
|
|
920
|
-
return;
|
|
921
|
-
items = items.filter((el) => el !== node);
|
|
922
|
-
const i = late.indexOf(node);
|
|
923
|
-
if (i >= 0) {
|
|
924
|
-
late.splice(i, 1);
|
|
925
|
-
sa.unobserve(node);
|
|
926
|
-
}
|
|
927
|
-
});
|
|
928
|
-
});
|
|
929
|
-
});
|
|
930
|
-
mo.observe(container, { childList: true });
|
|
931
|
-
}
|
|
932
|
-
return () => {
|
|
933
|
-
io.disconnect();
|
|
934
|
-
mo === null || mo === void 0 ? void 0 : mo.disconnect();
|
|
935
|
-
late.forEach((el) => sa.unobserve(el));
|
|
936
|
-
late.length = 0;
|
|
937
|
-
};
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
/**
|
|
941
|
-
* use-scroll-animate - Sequence / timeline helper
|
|
942
|
-
* Chain animations on several targets, one after another (or overlapping).
|
|
943
|
-
*/
|
|
944
|
-
let fallback = null;
|
|
945
|
-
function plan(steps, defaults) {
|
|
946
|
-
const out = [];
|
|
947
|
-
let cursor = 0;
|
|
948
|
-
steps.forEach((step) => {
|
|
949
|
-
var _a, _b;
|
|
950
|
-
const { target, gap = 0, at, ...stepOpts } = step;
|
|
951
|
-
const opts = { ...defaults, ...stepOpts };
|
|
952
|
-
const duration = (_a = opts.duration) !== null && _a !== void 0 ? _a : 600;
|
|
953
|
-
const start = Math.max(0, at !== null && at !== void 0 ? at : cursor + gap) + ((_b = opts.delay) !== null && _b !== void 0 ? _b : 0);
|
|
954
|
-
let end = Math.max(cursor, start);
|
|
955
|
-
resolveTargets(target).forEach((el, i) => {
|
|
956
|
-
var _a;
|
|
957
|
-
const delay = start + i * ((_a = opts.stagger) !== null && _a !== void 0 ? _a : 0);
|
|
958
|
-
out.push({ el, opts: { ...opts, duration, delay, stagger: 0 }, end: delay + duration });
|
|
959
|
-
end = Math.max(end, delay + duration);
|
|
960
|
-
});
|
|
961
|
-
cursor = end;
|
|
962
|
-
});
|
|
963
|
-
return out;
|
|
964
|
-
}
|
|
965
|
-
/**
|
|
966
|
-
* Build a timeline of animations.
|
|
967
|
-
*
|
|
968
|
-
* @example
|
|
969
|
-
* sequence([
|
|
970
|
-
* { target: '.title', animation: 'fade-in-up' },
|
|
971
|
-
* { target: '.subtitle', animation: 'blur-in', gap: -300 }, // overlap by 300ms
|
|
972
|
-
* { target: '.card', animation: 'scale-up', stagger: 80 },
|
|
973
|
-
* ], { trigger: '.hero' });
|
|
974
|
-
*/
|
|
975
|
-
function sequence(steps, options = {}) {
|
|
976
|
-
var _a, _b;
|
|
977
|
-
const { trigger, instance, ...defaults } = options;
|
|
978
|
-
const sa = () => instance || fallback || (fallback = createScrollAnimate());
|
|
979
|
-
let io;
|
|
980
|
-
let active = [];
|
|
981
|
-
let settle;
|
|
982
|
-
const controller = {
|
|
983
|
-
play() {
|
|
984
|
-
controller.cancel();
|
|
985
|
-
if (!hasDOM())
|
|
986
|
-
return Promise.resolve();
|
|
987
|
-
active = plan(steps, defaults);
|
|
988
|
-
return new Promise((resolve) => {
|
|
989
|
-
let left = active.length;
|
|
990
|
-
settle = () => {
|
|
991
|
-
settle = undefined;
|
|
992
|
-
resolve();
|
|
993
|
-
};
|
|
994
|
-
if (!left)
|
|
995
|
-
return settle();
|
|
996
|
-
const run = active;
|
|
997
|
-
run.forEach(({ el, opts }) => {
|
|
998
|
-
const done = opts.onComplete;
|
|
999
|
-
sa().animate(el, {
|
|
1000
|
-
...opts,
|
|
1001
|
-
onComplete: (node) => {
|
|
1002
|
-
done === null || done === void 0 ? void 0 : done(node);
|
|
1003
|
-
if (run === active && --left === 0)
|
|
1004
|
-
settle === null || settle === void 0 ? void 0 : settle();
|
|
1005
|
-
},
|
|
1006
|
-
});
|
|
1007
|
-
});
|
|
1008
|
-
});
|
|
1009
|
-
},
|
|
1010
|
-
cancel() {
|
|
1011
|
-
io === null || io === void 0 ? void 0 : io.disconnect();
|
|
1012
|
-
io = undefined;
|
|
1013
|
-
active.forEach(({ el }) => stopAnimation(el));
|
|
1014
|
-
active = [];
|
|
1015
|
-
settle === null || settle === void 0 ? void 0 : settle();
|
|
1016
|
-
},
|
|
1017
|
-
duration() {
|
|
1018
|
-
return plan(steps, defaults).reduce((max, p) => Math.max(max, p.end), 0);
|
|
1019
|
-
},
|
|
1020
|
-
};
|
|
1021
|
-
if (trigger && hasDOM() && supportsObserver()) {
|
|
1022
|
-
const el = resolveTargets(trigger)[0];
|
|
1023
|
-
if (el) {
|
|
1024
|
-
plan(steps, defaults).forEach(({ el: target }) => prepareElement(target));
|
|
1025
|
-
io = new IntersectionObserver((entries) => {
|
|
1026
|
-
if (!entries.some((e) => e.isIntersecting))
|
|
1027
|
-
return;
|
|
1028
|
-
io === null || io === void 0 ? void 0 : io.disconnect();
|
|
1029
|
-
io = undefined;
|
|
1030
|
-
controller.play();
|
|
1031
|
-
}, { threshold: (_a = defaults.threshold) !== null && _a !== void 0 ? _a : 0.1, rootMargin: (_b = defaults.rootMargin) !== null && _b !== void 0 ? _b : '0px' });
|
|
1032
|
-
io.observe(el);
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
return controller;
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
/**
|
|
1039
|
-
* use-scroll-animate - React Integration
|
|
1040
|
-
* Provides useScrollAnimate and useScrollStagger hooks for React applications.
|
|
1041
|
-
* `useScrollStagger({ observeChildren: true })` also animates children added later.
|
|
1042
|
-
*
|
|
1043
|
-
* Both hooks are thin wrappers around the core engine, so they share its
|
|
1044
|
-
* behaviour: `once`, `offset`, custom easing functions, parallax,
|
|
1045
|
-
* `prefers-reduced-motion` support, and proper cleanup on unmount.
|
|
1046
|
-
*/
|
|
1047
|
-
const CALLBACKS = ['onStart', 'onComplete', 'onEnter', 'onLeave', 'onProgress'];
|
|
1048
|
-
/**
|
|
1049
|
-
* Wrap the callbacks that exist at mount so they always call the latest
|
|
1050
|
-
* version from the most recent render (avoids stale closures without
|
|
1051
|
-
* re-creating observers on every render).
|
|
1052
|
-
* @internal
|
|
1053
|
-
*/
|
|
1054
|
-
function withLatestCallbacks(latest) {
|
|
1055
|
-
const initial = latest.current || {};
|
|
1056
|
-
const opts = { ...initial };
|
|
1057
|
-
CALLBACKS.forEach((name) => {
|
|
1058
|
-
if (typeof initial[name] === 'function') {
|
|
1059
|
-
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); };
|
|
1060
|
-
}
|
|
1061
|
-
});
|
|
1062
|
-
return opts;
|
|
1063
|
-
}
|
|
1064
|
-
function createReactHooks(React) {
|
|
1065
|
-
// Created lazily on the client so importing on the server is side-effect free.
|
|
1066
|
-
let instance = null;
|
|
1067
|
-
const getInstance = () => instance || (instance = createScrollAnimate());
|
|
1068
|
-
function useScrollAnimate(options = {}) {
|
|
1069
|
-
const ref = React.useRef(null);
|
|
1070
|
-
const optionsRef = React.useRef(options);
|
|
1071
|
-
optionsRef.current = options;
|
|
1072
|
-
React.useEffect(() => {
|
|
1073
|
-
const el = ref.current;
|
|
1074
|
-
if (!el)
|
|
1075
|
-
return;
|
|
1076
|
-
const sa = getInstance();
|
|
1077
|
-
sa.observe(el, withLatestCallbacks(optionsRef));
|
|
1078
|
-
return () => sa.unobserve(el);
|
|
1079
|
-
}, []);
|
|
1080
|
-
return ref;
|
|
1081
|
-
}
|
|
1082
|
-
function useScrollStagger(options = {}) {
|
|
1083
|
-
const ref = React.useRef(null);
|
|
1084
|
-
const optionsRef = React.useRef(options);
|
|
1085
|
-
optionsRef.current = options;
|
|
1086
|
-
React.useEffect(() => {
|
|
1087
|
-
const container = ref.current;
|
|
1088
|
-
if (!container)
|
|
1089
|
-
return;
|
|
1090
|
-
return staggerChildren(container, withLatestCallbacks(optionsRef), getInstance());
|
|
1091
|
-
}, []);
|
|
1092
|
-
return ref;
|
|
1093
|
-
}
|
|
1094
|
-
return { useScrollAnimate, useScrollStagger };
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
/**
|
|
1098
|
-
* use-scroll-animate - Vue 3 Integration
|
|
1099
|
-
* Provides useScrollAnimate and useScrollStagger composables for Vue 3 applications.
|
|
1100
|
-
*
|
|
1101
|
-
* A thin wrapper around the core engine, so it shares its behaviour: `once`,
|
|
1102
|
-
* `offset`, custom easing functions, parallax, `prefers-reduced-motion`
|
|
1103
|
-
* support, and cleanup on unmount.
|
|
1104
|
-
*/
|
|
1105
|
-
/** Support refs on components (`$el`) as well as plain elements. */
|
|
1106
|
-
function unwrap(value) {
|
|
1107
|
-
if (value && typeof Element !== 'undefined' && !(value instanceof Element) && value.$el instanceof Element) {
|
|
1108
|
-
return value.$el;
|
|
1109
|
-
}
|
|
1110
|
-
return value || null;
|
|
1111
|
-
}
|
|
1112
|
-
function createVueComposables(Vue) {
|
|
1113
|
-
// Created lazily on the client so importing on the server is side-effect free.
|
|
1114
|
-
let instance = null;
|
|
1115
|
-
const getInstance = () => instance || (instance = createScrollAnimate());
|
|
1116
|
-
function useScrollAnimate(options = {}) {
|
|
1117
|
-
const animateRef = Vue.ref(null);
|
|
1118
|
-
let el = null;
|
|
1119
|
-
Vue.onMounted(() => {
|
|
1120
|
-
const target = unwrap(animateRef.value);
|
|
1121
|
-
if (!target)
|
|
1122
|
-
return;
|
|
1123
|
-
el = target;
|
|
1124
|
-
getInstance().observe(el, options);
|
|
1125
|
-
});
|
|
1126
|
-
Vue.onUnmounted(() => {
|
|
1127
|
-
if (el)
|
|
1128
|
-
getInstance().unobserve(el);
|
|
1129
|
-
el = null;
|
|
1130
|
-
});
|
|
1131
|
-
return { animateRef };
|
|
1132
|
-
}
|
|
1133
|
-
/** Stagger the children of `staggerRef`; `observeChildren: true` also animates children added later. */
|
|
1134
|
-
function useScrollStagger(options = {}) {
|
|
1135
|
-
const staggerRef = Vue.ref(null);
|
|
1136
|
-
let stop;
|
|
1137
|
-
Vue.onMounted(() => {
|
|
1138
|
-
const target = unwrap(staggerRef.value);
|
|
1139
|
-
if (target)
|
|
1140
|
-
stop = staggerChildren(target, options, getInstance());
|
|
1141
|
-
});
|
|
1142
|
-
Vue.onUnmounted(() => {
|
|
1143
|
-
stop === null || stop === void 0 ? void 0 : stop();
|
|
1144
|
-
stop = undefined;
|
|
1145
|
-
});
|
|
1146
|
-
return { staggerRef };
|
|
1147
|
-
}
|
|
1148
|
-
return { useScrollAnimate, useScrollStagger };
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
/**
|
|
1152
|
-
* use-scroll-animate
|
|
1153
|
-
*
|
|
1154
|
-
* A lightweight, dependency-free scroll animation library for modern web
|
|
1155
|
-
* applications. Built with TypeScript, powered by IntersectionObserver and
|
|
1156
|
-
* the Web Animations API. Safe to import during SSR.
|
|
1157
|
-
*
|
|
1158
|
-
* @license MIT
|
|
1159
|
-
* @see https://github.com/HarrisonCN/use-scroll-animate
|
|
1160
|
-
*/
|
|
1161
|
-
/**
|
|
1162
|
-
* Default singleton instance of ScrollAnimate.
|
|
1163
|
-
* Ready to use out of the box with sensible defaults.
|
|
1164
|
-
*
|
|
1165
|
-
* @example
|
|
1166
|
-
* ```js
|
|
1167
|
-
* import ScrollAnimate from 'use-scroll-animate';
|
|
1168
|
-
*
|
|
1169
|
-
* // Auto-initialize all elements with data-sa attribute
|
|
1170
|
-
* ScrollAnimate.init();
|
|
1171
|
-
*
|
|
1172
|
-
* // Or manually observe elements
|
|
1173
|
-
* ScrollAnimate.observe('.my-element', { animation: 'fade-in-up' });
|
|
1174
|
-
* ```
|
|
1175
|
-
*/
|
|
1176
|
-
const ScrollAnimate = createScrollAnimate();
|
|
1177
|
-
|
|
1178
|
-
export { EASING_MAP, PRESETS, createReactHooks, createScrollAnimate, createVueComposables, ScrollAnimate as default, getScrollProgress, resolveEasing, resolvePreset, sequence, staggerChildren };
|
|
1179
|
-
//# sourceMappingURL=index.mjs.map
|
|
1138
|
+
exports.EASING_MAP = EASING_MAP;
|
|
1139
|
+
exports.PRESETS = PRESETS;
|
|
1140
|
+
exports.createScrollAnimate = createScrollAnimate;
|
|
1141
|
+
exports.getScrollProgress = getScrollProgress;
|
|
1142
|
+
exports.hasDOM = hasDOM;
|
|
1143
|
+
exports.prefersReducedMotion = prefersReducedMotion;
|
|
1144
|
+
exports.prepareElement = prepareElement;
|
|
1145
|
+
exports.readOptions = readOptions;
|
|
1146
|
+
exports.resolveEasing = resolveEasing;
|
|
1147
|
+
exports.resolvePreset = resolvePreset;
|
|
1148
|
+
exports.resolveTargets = resolveTargets;
|
|
1149
|
+
exports.stopAnimation = stopAnimation;
|
|
1150
|
+
exports.supportsObserver = supportsObserver;
|
|
1151
|
+
exports.supportsScrollTimeline = supportsScrollTimeline;
|
|
1152
|
+
//# sourceMappingURL=core-CH41ekIo.cjs.map
|