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
|
@@ -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
|
/* ------------------------------------------------------------------ */
|
|
@@ -252,23 +268,37 @@ function lengthValue(value) {
|
|
|
252
268
|
const v = value.trim();
|
|
253
269
|
return /^-?(\d+\.?\d*|\.\d+)$/.test(v) ? parseFloat(v) : v;
|
|
254
270
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
271
|
+
const DEFAULT_PROGRESS_VAR = '--sa-progress';
|
|
272
|
+
const DEFAULT_VIEW_RANGE = ['entry 0%', 'entry 100%'];
|
|
273
|
+
/** `''` (bare attribute) -> default name; `sa-progress` -> `--sa-progress`. */
|
|
274
|
+
function normalizeVar(name) {
|
|
275
|
+
const v = name.trim();
|
|
276
|
+
if (!v)
|
|
277
|
+
return DEFAULT_PROGRESS_VAR;
|
|
278
|
+
return v.startsWith('--') ? v : `--${v}`;
|
|
279
|
+
}
|
|
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) {
|
|
258
287
|
const opts = {};
|
|
259
|
-
|
|
260
|
-
|
|
288
|
+
const anim = get('animation');
|
|
289
|
+
if (anim) {
|
|
261
290
|
opts.animation = (anim.includes(',') ? anim.split(',').map((s) => s.trim()) : anim.trim());
|
|
262
291
|
}
|
|
263
|
-
opts.duration = num(
|
|
264
|
-
opts.delay = num(
|
|
265
|
-
|
|
266
|
-
|
|
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();
|
|
267
297
|
if (e.startsWith('[')) {
|
|
268
298
|
try {
|
|
269
299
|
opts.easing = JSON.parse(e);
|
|
270
300
|
}
|
|
271
|
-
catch
|
|
301
|
+
catch {
|
|
272
302
|
// Malformed JSON: fall back to the default easing instead of throwing
|
|
273
303
|
}
|
|
274
304
|
}
|
|
@@ -276,55 +306,95 @@ function parseDataAttributes(el, config) {
|
|
|
276
306
|
opts.easing = e;
|
|
277
307
|
}
|
|
278
308
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
opts.threshold = t.includes(',')
|
|
282
|
-
? t.split(',').map(parseFloat).filter(Number.isFinite)
|
|
283
|
-
: num(t);
|
|
309
|
+
const t = get('threshold');
|
|
310
|
+
if (t) {
|
|
311
|
+
opts.threshold = t.includes(',') ? t.split(',').map(parseFloat).filter(Number.isFinite) : num(t);
|
|
284
312
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (
|
|
290
|
-
opts.
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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) {
|
|
296
349
|
opts.parallax = {
|
|
297
|
-
x: lengthValue(
|
|
298
|
-
y: lengthValue(
|
|
299
|
-
rotate: num(
|
|
300
|
-
scale: num(
|
|
301
|
-
speed:
|
|
350
|
+
x: lengthValue(px),
|
|
351
|
+
y: lengthValue(py),
|
|
352
|
+
rotate: num(pr),
|
|
353
|
+
scale: num(ps),
|
|
354
|
+
speed: num(get('parallax-speed')) ?? 1,
|
|
302
355
|
};
|
|
303
356
|
}
|
|
304
|
-
|
|
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);
|
|
305
366
|
}
|
|
306
367
|
function mergeOptions(opts, config) {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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;
|
|
310
376
|
return {
|
|
311
|
-
animation:
|
|
312
|
-
duration:
|
|
313
|
-
delay:
|
|
314
|
-
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,
|
|
315
381
|
threshold: Array.isArray(threshold) && threshold.length === 0 ? config.defaultThreshold : threshold,
|
|
316
|
-
rootMargin:
|
|
382
|
+
rootMargin: opts.rootMargin ?? config.defaultRootMargin,
|
|
317
383
|
repeat,
|
|
318
|
-
once:
|
|
319
|
-
offset:
|
|
320
|
-
stagger:
|
|
321
|
-
parallax:
|
|
322
|
-
onStart:
|
|
323
|
-
onComplete:
|
|
324
|
-
onEnter:
|
|
325
|
-
onLeave:
|
|
326
|
-
onProgress:
|
|
327
|
-
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',
|
|
394
|
+
progressVar: opts.progressVar ? normalizeVar(opts.progressVar) : '',
|
|
395
|
+
engine,
|
|
396
|
+
viewRange: opts.viewRange ?? DEFAULT_VIEW_RANGE,
|
|
397
|
+
exit,
|
|
328
398
|
};
|
|
329
399
|
}
|
|
330
400
|
function resolveTargets(target) {
|
|
@@ -345,14 +415,13 @@ function resolveTargets(target) {
|
|
|
345
415
|
* @internal
|
|
346
416
|
*/
|
|
347
417
|
function applyOffset(rootMargin, offset) {
|
|
348
|
-
var _a, _b, _c;
|
|
349
418
|
if (!offset)
|
|
350
419
|
return rootMargin;
|
|
351
420
|
const parts = (rootMargin || '0px').trim().split(/\s+/);
|
|
352
421
|
const top = parts[0];
|
|
353
|
-
const right =
|
|
354
|
-
const bottom =
|
|
355
|
-
const left =
|
|
422
|
+
const right = parts[1] ?? top;
|
|
423
|
+
const bottom = parts[2] ?? top;
|
|
424
|
+
const left = parts[3] ?? right;
|
|
356
425
|
const m = /^(-?\d*\.?\d+)(px)?$/.exec(bottom);
|
|
357
426
|
const base = m ? parseFloat(m[1]) : 0; // non-px bottoms (e.g. %) cannot be combined; offset wins
|
|
358
427
|
return `${top} ${right} ${base - offset}px ${left}`;
|
|
@@ -361,7 +430,7 @@ function hasParallax(p) {
|
|
|
361
430
|
return !!p && Object.keys(p).some((k) => p[k] !== undefined);
|
|
362
431
|
}
|
|
363
432
|
function needsProgress(opts) {
|
|
364
|
-
return hasParallax(opts.parallax) || opts.onProgress !== noop;
|
|
433
|
+
return hasParallax(opts.parallax) || opts.onProgress !== noop || !!opts.progressVar;
|
|
365
434
|
}
|
|
366
435
|
/**
|
|
367
436
|
* True scroll progress of `el` through the viewport (or `root`): 0 when its top
|
|
@@ -390,8 +459,15 @@ function getScrollProgress(el, root) {
|
|
|
390
459
|
/* ------------------------------------------------------------------ */
|
|
391
460
|
/** The animation currently running on an element, so it can be cancelled/replaced. */
|
|
392
461
|
const running = new WeakMap();
|
|
462
|
+
/** Native engine: the scroll-linked exit animation, next to the entrance in `running`. */
|
|
463
|
+
const exits = new WeakMap();
|
|
393
464
|
const timers = new WeakMap();
|
|
394
465
|
function cancelRunning(el) {
|
|
466
|
+
const exitAnim = exits.get(el);
|
|
467
|
+
if (exitAnim) {
|
|
468
|
+
exits.delete(el);
|
|
469
|
+
exitAnim.cancel();
|
|
470
|
+
}
|
|
395
471
|
const anim = running.get(el);
|
|
396
472
|
if (anim) {
|
|
397
473
|
running.delete(el);
|
|
@@ -502,7 +578,7 @@ function stopAnimation(el, config = {}) {
|
|
|
502
578
|
function motionDisabled(config) {
|
|
503
579
|
return !!config.disabled || prefersReducedMotion();
|
|
504
580
|
}
|
|
505
|
-
function runAnimation(el, opts, config, staggerIndex = 0) {
|
|
581
|
+
function runAnimation(el, opts, config, staggerIndex = 0, pending) {
|
|
506
582
|
const { duration, delay, stagger, onStart, onComplete } = opts;
|
|
507
583
|
const totalDelay = Math.max(0, delay + staggerIndex * stagger);
|
|
508
584
|
cancelRunning(el);
|
|
@@ -518,8 +594,10 @@ function runAnimation(el, opts, config, staggerIndex = 0) {
|
|
|
518
594
|
el.classList.remove(config.hiddenClass);
|
|
519
595
|
el.classList.add(config.visibleClass);
|
|
520
596
|
onStart(el);
|
|
597
|
+
pending?.add(el);
|
|
521
598
|
timers.set(el, setTimeout(() => {
|
|
522
599
|
timers.delete(el);
|
|
600
|
+
pending?.delete(el);
|
|
523
601
|
onComplete(el);
|
|
524
602
|
}, duration + totalDelay));
|
|
525
603
|
return;
|
|
@@ -544,7 +622,7 @@ function runAnimation(el, opts, config, staggerIndex = 0) {
|
|
|
544
622
|
try {
|
|
545
623
|
anim = el.animate(built.keyframes, timing);
|
|
546
624
|
}
|
|
547
|
-
catch
|
|
625
|
+
catch {
|
|
548
626
|
// Invalid user easing string (WAAPI throws a TypeError): fall back to 'ease'
|
|
549
627
|
anim = el.animate(built.keyframes, { ...timing, easing: 'ease' });
|
|
550
628
|
}
|
|
@@ -559,7 +637,7 @@ function runAnimation(el, opts, config, staggerIndex = 0) {
|
|
|
559
637
|
try {
|
|
560
638
|
anim.commitStyles();
|
|
561
639
|
}
|
|
562
|
-
catch
|
|
640
|
+
catch {
|
|
563
641
|
setStyles(el, preset.to);
|
|
564
642
|
}
|
|
565
643
|
anim.cancel();
|
|
@@ -567,6 +645,69 @@ function runAnimation(el, opts, config, staggerIndex = 0) {
|
|
|
567
645
|
onComplete(el);
|
|
568
646
|
};
|
|
569
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
|
+
}
|
|
570
711
|
function applyParallax(el, progress, parallax) {
|
|
571
712
|
const { x = 0, y = 0, rotate = 0, scale = 1, speed = 1 } = parallax;
|
|
572
713
|
const p = (progress - 0.5) * 2 * speed;
|
|
@@ -582,6 +723,26 @@ function applyParallax(el, progress, parallax) {
|
|
|
582
723
|
transform += ` scale(${1 + (scale - 1) * p})`;
|
|
583
724
|
el.style.transform = transform.trim();
|
|
584
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
|
+
}
|
|
585
746
|
function hideElement(el, config) {
|
|
586
747
|
cancelRunning(el);
|
|
587
748
|
if (config.useClassNames) {
|
|
@@ -622,6 +783,12 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
622
783
|
const scrolling = new Set();
|
|
623
784
|
let frame = 0;
|
|
624
785
|
let listening = null;
|
|
786
|
+
// Active watch() MutationObservers, disconnected by destroy().
|
|
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();
|
|
625
792
|
// Observers are shared between elements with the same root/threshold/rootMargin,
|
|
626
793
|
// instead of one (or two) IntersectionObservers per element.
|
|
627
794
|
const pools = new Map();
|
|
@@ -635,14 +802,22 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
635
802
|
return io;
|
|
636
803
|
}
|
|
637
804
|
function teardown(el, restore) {
|
|
638
|
-
var _a;
|
|
639
805
|
const record = registry.get(el);
|
|
640
806
|
if (!record)
|
|
641
807
|
return;
|
|
642
808
|
record.observer.unobserve(el);
|
|
643
|
-
|
|
809
|
+
record.progressObserver?.unobserve(el);
|
|
644
810
|
registry.delete(el);
|
|
645
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
|
+
return;
|
|
820
|
+
}
|
|
646
821
|
// An element that never animated would otherwise stay invisible forever.
|
|
647
822
|
if (restore && !record.animated)
|
|
648
823
|
reveal(el, config);
|
|
@@ -656,6 +831,11 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
656
831
|
function emitProgress(el, record, progress) {
|
|
657
832
|
const opts = record.options;
|
|
658
833
|
opts.onProgress(el, progress);
|
|
834
|
+
if (opts.progressVar) {
|
|
835
|
+
const style = el.style;
|
|
836
|
+
if (style)
|
|
837
|
+
style.setProperty(opts.progressVar, String(+progress.toFixed(4)));
|
|
838
|
+
}
|
|
659
839
|
if (hasParallax(opts.parallax) && !motionDisabled(config))
|
|
660
840
|
applyParallax(el, progress, opts.parallax);
|
|
661
841
|
}
|
|
@@ -706,7 +886,6 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
706
886
|
function onIntersect(entries) {
|
|
707
887
|
const staggerCounts = new Map();
|
|
708
888
|
entries.forEach((entry) => {
|
|
709
|
-
var _a;
|
|
710
889
|
const el = entry.target;
|
|
711
890
|
const record = registry.get(el);
|
|
712
891
|
if (!record)
|
|
@@ -716,6 +895,29 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
716
895
|
teardown(el, false);
|
|
717
896
|
return;
|
|
718
897
|
}
|
|
898
|
+
if (record.engine === 'css') {
|
|
899
|
+
// The browser drives the animation; only lifecycle bookkeeping here.
|
|
900
|
+
if (entry.isIntersecting) {
|
|
901
|
+
opts.onEnter(el);
|
|
902
|
+
if (!record.animated) {
|
|
903
|
+
record.animated = true;
|
|
904
|
+
opts.onStart(el);
|
|
905
|
+
}
|
|
906
|
+
if (opts.once && !opts.repeat) {
|
|
907
|
+
record.observer.unobserve(el);
|
|
908
|
+
if (config.autoUnregister && !record.progressObserver) {
|
|
909
|
+
finished.add(el);
|
|
910
|
+
teardown(el, false);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
else {
|
|
915
|
+
opts.onLeave(el);
|
|
916
|
+
if (opts.repeat)
|
|
917
|
+
record.animated = false;
|
|
918
|
+
}
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
719
921
|
if (entry.isIntersecting) {
|
|
720
922
|
opts.onEnter(el);
|
|
721
923
|
if (record.animated && !opts.repeat)
|
|
@@ -725,10 +927,10 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
725
927
|
let staggerIndex = 0;
|
|
726
928
|
if (opts.stagger > 0) {
|
|
727
929
|
const parent = el.parentElement;
|
|
728
|
-
staggerIndex =
|
|
930
|
+
staggerIndex = staggerCounts.get(parent) ?? 0;
|
|
729
931
|
staggerCounts.set(parent, staggerIndex + 1);
|
|
730
932
|
}
|
|
731
|
-
runAnimation(el, opts, config, staggerIndex);
|
|
933
|
+
runAnimation(el, opts, config, staggerIndex, pending);
|
|
732
934
|
record.animated = true;
|
|
733
935
|
if (opts.once && !opts.repeat) {
|
|
734
936
|
// Keep the progress observer: parallax/onProgress must keep working.
|
|
@@ -744,8 +946,12 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
744
946
|
else {
|
|
745
947
|
opts.onLeave(el);
|
|
746
948
|
if (opts.repeat && record.animated) {
|
|
747
|
-
if (!motionDisabled(config))
|
|
748
|
-
|
|
949
|
+
if (!motionDisabled(config)) {
|
|
950
|
+
if (opts.exit)
|
|
951
|
+
runExit(el, opts, config);
|
|
952
|
+
else
|
|
953
|
+
hideElement(el, config);
|
|
954
|
+
}
|
|
749
955
|
record.animated = false;
|
|
750
956
|
}
|
|
751
957
|
}
|
|
@@ -779,12 +985,14 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
779
985
|
}));
|
|
780
986
|
}
|
|
781
987
|
function attach(el, record, observeMain) {
|
|
782
|
-
var _a;
|
|
783
988
|
record.observer = getObserver(record.options);
|
|
784
989
|
if (observeMain)
|
|
785
990
|
record.observer.observe(el);
|
|
786
991
|
record.progressObserver = needsProgress(record.options) ? getProgressObserver(record.options) : undefined;
|
|
787
|
-
|
|
992
|
+
record.progressObserver?.observe(el);
|
|
993
|
+
}
|
|
994
|
+
function wantsNative(opts) {
|
|
995
|
+
return opts.engine !== 'js' && !config.useClassNames && !motionDisabled(config) && supportsScrollTimeline();
|
|
788
996
|
}
|
|
789
997
|
function observeElement(el, opts) {
|
|
790
998
|
if (registry.has(el) || finished.has(el))
|
|
@@ -794,12 +1002,23 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
794
1002
|
reveal(el, config);
|
|
795
1003
|
return;
|
|
796
1004
|
}
|
|
797
|
-
|
|
1005
|
+
const record = { element: el, options: opts, animated: false, engine: 'js' };
|
|
1006
|
+
if (wantsNative(opts)) {
|
|
1007
|
+
cancelRunning(el);
|
|
1008
|
+
if (startNative(el, opts, () => natives.delete(el))) {
|
|
1009
|
+
record.engine = 'css';
|
|
1010
|
+
natives.add(el);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
if (record.engine === 'js' && !motionDisabled(config))
|
|
798
1014
|
hideElement(el, config);
|
|
799
|
-
const record = { element: el, options: opts, animated: false };
|
|
800
1015
|
registry.set(el, record);
|
|
801
1016
|
attach(el, record, true);
|
|
802
1017
|
}
|
|
1018
|
+
function observeDataElement(el) {
|
|
1019
|
+
if (!registry.has(el))
|
|
1020
|
+
observeElement(el, parseDataAttributes(el, config));
|
|
1021
|
+
}
|
|
803
1022
|
const instance = {
|
|
804
1023
|
observe(target, options = {}) {
|
|
805
1024
|
pruneDetached();
|
|
@@ -813,20 +1032,71 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
813
1032
|
});
|
|
814
1033
|
},
|
|
815
1034
|
init(rootElement) {
|
|
816
|
-
const scope = rootElement
|
|
1035
|
+
const scope = rootElement ?? (hasDOM() ? document : null);
|
|
817
1036
|
if (!scope)
|
|
818
1037
|
return;
|
|
819
1038
|
pruneDetached();
|
|
820
|
-
scope.querySelectorAll('[data-sa]').forEach(
|
|
821
|
-
|
|
822
|
-
|
|
1039
|
+
scope.querySelectorAll('[data-sa]').forEach(observeDataElement);
|
|
1040
|
+
},
|
|
1041
|
+
watch(rootElement) {
|
|
1042
|
+
const scope = rootElement ?? (hasDOM() ? document : null);
|
|
1043
|
+
if (!scope || typeof MutationObserver === 'undefined')
|
|
1044
|
+
return noop;
|
|
1045
|
+
instance.init(scope);
|
|
1046
|
+
const observeTree = (node) => {
|
|
1047
|
+
if (node.hasAttribute('data-sa'))
|
|
1048
|
+
observeDataElement(node);
|
|
1049
|
+
node.querySelectorAll('[data-sa]').forEach(observeDataElement);
|
|
1050
|
+
};
|
|
1051
|
+
const mo = new MutationObserver((records) => {
|
|
1052
|
+
let removed = false;
|
|
1053
|
+
records.forEach((record) => {
|
|
1054
|
+
if (record.type === 'attributes') {
|
|
1055
|
+
const target = record.target;
|
|
1056
|
+
if (target.isConnected !== false)
|
|
1057
|
+
observeTree(target);
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
record.addedNodes.forEach((node) => {
|
|
1061
|
+
if (node instanceof Element && node.isConnected !== false)
|
|
1062
|
+
observeTree(node);
|
|
1063
|
+
});
|
|
1064
|
+
if (record.removedNodes.length)
|
|
1065
|
+
removed = true;
|
|
1066
|
+
});
|
|
1067
|
+
// Free elements that left the DOM (they can't animate any more).
|
|
1068
|
+
if (removed)
|
|
1069
|
+
pruneDetached();
|
|
823
1070
|
});
|
|
1071
|
+
mo.observe(scope, { childList: true, subtree: true, attributes: true, attributeFilter: ['data-sa'] });
|
|
1072
|
+
watchers.add(mo);
|
|
1073
|
+
return () => {
|
|
1074
|
+
mo.disconnect();
|
|
1075
|
+
watchers.delete(mo);
|
|
1076
|
+
};
|
|
824
1077
|
},
|
|
825
1078
|
destroy() {
|
|
1079
|
+
watchers.forEach((mo) => mo.disconnect());
|
|
1080
|
+
watchers.clear();
|
|
1081
|
+
// Class-name mode: drop completion timers so onComplete never fires after destroy().
|
|
1082
|
+
pending.forEach((el) => {
|
|
1083
|
+
const timer = timers.get(el);
|
|
1084
|
+
if (timer !== undefined) {
|
|
1085
|
+
clearTimeout(timer);
|
|
1086
|
+
timers.delete(el);
|
|
1087
|
+
}
|
|
1088
|
+
});
|
|
1089
|
+
pending.clear();
|
|
826
1090
|
registry.forEach((record, el) => {
|
|
827
|
-
if (!record.animated)
|
|
1091
|
+
if (!record.animated && record.engine !== 'css')
|
|
1092
|
+
reveal(el, config);
|
|
1093
|
+
});
|
|
1094
|
+
// Native scroll-linked animations still running: stop them, show content.
|
|
1095
|
+
natives.forEach((el) => {
|
|
1096
|
+
if (running.has(el))
|
|
828
1097
|
reveal(el, config);
|
|
829
1098
|
});
|
|
1099
|
+
natives.clear();
|
|
830
1100
|
pools.forEach((pool) => pool.forEach((io) => io.disconnect()));
|
|
831
1101
|
pools.clear();
|
|
832
1102
|
registry.clear();
|
|
@@ -851,7 +1121,7 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
851
1121
|
},
|
|
852
1122
|
animate(target, options = {}) {
|
|
853
1123
|
const opts = mergeOptions(options, config);
|
|
854
|
-
resolveTargets(target).forEach((el) => runAnimation(el, opts, config, 0));
|
|
1124
|
+
resolveTargets(target).forEach((el) => runAnimation(el, opts, config, 0, pending));
|
|
855
1125
|
},
|
|
856
1126
|
getObservedElements() {
|
|
857
1127
|
return Array.from(registry.values());
|
|
@@ -863,317 +1133,5 @@ function createScrollAnimate(userConfig = {}) {
|
|
|
863
1133
|
return instance;
|
|
864
1134
|
}
|
|
865
1135
|
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
* Reveal a container's children one after another when the container scrolls
|
|
869
|
-
* into view, optionally also animating children that are added later.
|
|
870
|
-
*/
|
|
871
|
-
let fallback$1 = null;
|
|
872
|
-
/**
|
|
873
|
-
* Animate the children of `container` with a stagger once it enters the
|
|
874
|
-
* viewport. Returns a cleanup function. SSR-safe (no-op without a DOM).
|
|
875
|
-
*
|
|
876
|
-
* @example
|
|
877
|
-
* const stop = staggerChildren(document.querySelector('ul'), { stagger: 60, observeChildren: true });
|
|
878
|
-
*/
|
|
879
|
-
function staggerChildren(container, options = {}, instance) {
|
|
880
|
-
if (!container || !hasDOM() || !supportsObserver())
|
|
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.esm.js.map
|
|
1136
|
+
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 };
|
|
1137
|
+
//# sourceMappingURL=core-FUEi4ncH.js.map
|