use-scroll-animate 5.1.0 → 5.3.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 +21 -0
- package/dist/components/effects.cjs +407 -0
- package/dist/components/effects.cjs.map +1 -1
- package/dist/components/effects.d.cts +52 -1
- package/dist/components/effects.d.ts +52 -1
- package/dist/components/effects.js +401 -1
- package/dist/components/effects.js.map +1 -1
- package/dist/components.umd.js +2 -2
- package/dist/components.umd.js.map +1 -1
- package/docs/ROADMAP.md +2 -2
- package/docs/components.md +31 -0
- package/package.json +1 -1
|
@@ -227,6 +227,396 @@ const CLICK_FX = [
|
|
|
227
227
|
},
|
|
228
228
|
];
|
|
229
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Sample a damped spring from 0 → 1 and return the progress values plus the
|
|
232
|
+
* time (ms) it takes to settle. Pure, deterministic.
|
|
233
|
+
*/
|
|
234
|
+
function solveSpring({ stiffness = 180, damping = 12, mass = 1, steps = 40 } = {}) {
|
|
235
|
+
const dt = 1 / 120;
|
|
236
|
+
let x = 0;
|
|
237
|
+
let v = 0;
|
|
238
|
+
const trace = [];
|
|
239
|
+
let t = 0;
|
|
240
|
+
for (; t < 4; t += dt) {
|
|
241
|
+
const a = (-stiffness * (x - 1) - damping * v) / mass;
|
|
242
|
+
v += a * dt;
|
|
243
|
+
x += v * dt;
|
|
244
|
+
trace.push(x);
|
|
245
|
+
if (t > 0.1 && Math.abs(x - 1) < 0.001 && Math.abs(v) < 0.01)
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
const values = Array.from({ length: steps + 1 }, (_, i) => +trace[Math.min(trace.length - 1, Math.round((i / steps) * (trace.length - 1)))].toFixed(4));
|
|
249
|
+
values[0] = 0;
|
|
250
|
+
values[steps] = 1;
|
|
251
|
+
return { values, duration: Math.round(trace.length * dt * 1000) };
|
|
252
|
+
}
|
|
253
|
+
/** Keyframes for a property driven by a spring: `map(progress)` → keyframe. */
|
|
254
|
+
function springKeyframes(map, spring) {
|
|
255
|
+
const { values, duration } = solveSpring(spring);
|
|
256
|
+
return { frames: values.map(map), duration };
|
|
257
|
+
}
|
|
258
|
+
/** Height (0 = floor, 1 = drop height) of a ball dropped with restitution `bounce`, sampled `steps` times. */
|
|
259
|
+
function bounceKeyframes(bounce = 0.5, steps = 48) {
|
|
260
|
+
// segment durations scale with sqrt(height); heights scale with bounce^2 per hop
|
|
261
|
+
const hops = [1];
|
|
262
|
+
let h = 1;
|
|
263
|
+
while (hops.length < 6 && h > 0.01) {
|
|
264
|
+
h *= bounce * bounce;
|
|
265
|
+
hops.push(h);
|
|
266
|
+
}
|
|
267
|
+
const segT = hops.map((hh, i) => (i === 0 ? Math.sqrt(hh) : 2 * Math.sqrt(hh)));
|
|
268
|
+
const total = segT.reduce((a, b) => a + b, 0);
|
|
269
|
+
return Array.from({ length: steps + 1 }, (_, i) => {
|
|
270
|
+
let t = (i / steps) * total;
|
|
271
|
+
for (let k = 0; k < hops.length; k++) {
|
|
272
|
+
if (t <= segT[k] || k === hops.length - 1) {
|
|
273
|
+
const tt = Math.min(t, segT[k]);
|
|
274
|
+
if (k === 0)
|
|
275
|
+
return +(1 - (tt / segT[0]) ** 2).toFixed(4);
|
|
276
|
+
const half = segT[k] / 2;
|
|
277
|
+
return +(hops[k] * (1 - ((tt - half) / half) ** 2)).toFixed(4);
|
|
278
|
+
}
|
|
279
|
+
t -= segT[k];
|
|
280
|
+
}
|
|
281
|
+
return 0;
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
const fade = (el, ctx) => ctx.animate(el, [{ opacity: 0 }, { opacity: 1 }], { duration: 200 });
|
|
285
|
+
const PHYSICS_FX = [
|
|
286
|
+
{
|
|
287
|
+
name: 'bounce-in',
|
|
288
|
+
kind: 'enter',
|
|
289
|
+
description: 'Springs in from small with an overshoot (spring solver; stiffness / damping options).',
|
|
290
|
+
defaults: { stiffness: 220, damping: 11, from: 0.3 },
|
|
291
|
+
run: (el, o, ctx) => {
|
|
292
|
+
if (ctx.reduced)
|
|
293
|
+
return fade(el, ctx);
|
|
294
|
+
const { frames, duration } = springKeyframes((p) => ({ transform: `scale(${(o.from + (1 - o.from) * p).toFixed(4)})`, opacity: Math.min(1, p * 3) }), o);
|
|
295
|
+
return ctx.animate(el, frames, { duration, easing: 'linear' });
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
name: 'rubber-band',
|
|
300
|
+
kind: 'attention',
|
|
301
|
+
description: 'Stretches wide, snaps back thin and settles like a rubber band.',
|
|
302
|
+
defaults: { duration: 900, amount: 0.25 },
|
|
303
|
+
run: (el, o, ctx) => {
|
|
304
|
+
if (ctx.reduced)
|
|
305
|
+
return;
|
|
306
|
+
const a = o.amount;
|
|
307
|
+
return ctx.animate(el, [{ transform: 'scale(1,1)' }, { transform: `scale(${1 + a},${1 - a})`, offset: 0.3 }, { transform: `scale(${1 - a},${1 + a})`, offset: 0.4 }, { transform: `scale(${1 + a / 2},${1 - a / 2})`, offset: 0.5 }, { transform: `scale(${1 - a / 5},${1 + a / 5})`, offset: 0.65 }, { transform: `scale(${1 + a / 10},${1 - a / 10})`, offset: 0.75 }, { transform: 'scale(1,1)' }], { duration: o.duration });
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
name: 'elastic-hover',
|
|
312
|
+
kind: 'hover',
|
|
313
|
+
description: 'Hover lifts with a springy overshoot; leaving springs back (persistent; trigger="load").',
|
|
314
|
+
defaults: { lift: 6, scale: 1.04, stiffness: 260, damping: 10 },
|
|
315
|
+
run: (el, o, ctx) => {
|
|
316
|
+
if (ctx.reduced)
|
|
317
|
+
return;
|
|
318
|
+
const to = (on) => {
|
|
319
|
+
const from = on ? 0 : 1;
|
|
320
|
+
const { frames, duration } = springKeyframes((p) => {
|
|
321
|
+
const k = from + (on ? p : -p);
|
|
322
|
+
return { transform: `translateY(${(-o.lift * k).toFixed(2)}px) scale(${(1 + (o.scale - 1) * k).toFixed(4)})` };
|
|
323
|
+
}, o);
|
|
324
|
+
ctx.animate(el, frames, { duration, fill: 'forwards', easing: 'linear' });
|
|
325
|
+
};
|
|
326
|
+
const enter = () => to(true);
|
|
327
|
+
const leave = () => to(false);
|
|
328
|
+
el.addEventListener('pointerenter', enter);
|
|
329
|
+
el.addEventListener('pointerleave', leave);
|
|
330
|
+
return () => {
|
|
331
|
+
el.removeEventListener('pointerenter', enter);
|
|
332
|
+
el.removeEventListener('pointerleave', leave);
|
|
333
|
+
};
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
name: 'drop-bounce',
|
|
338
|
+
kind: 'enter',
|
|
339
|
+
description: 'Falls in under gravity and bounces to rest (`height`, `bounce` restitution 0–0.9).',
|
|
340
|
+
defaults: { height: 120, bounce: 0.5, duration: 1100 },
|
|
341
|
+
run: (el, o, ctx) => {
|
|
342
|
+
if (ctx.reduced)
|
|
343
|
+
return fade(el, ctx);
|
|
344
|
+
const hs = bounceKeyframes(Math.min(0.9, Math.max(0, o.bounce)));
|
|
345
|
+
return ctx.animate(el, hs.map((h, i) => ({ transform: `translateY(${(-h * o.height).toFixed(1)}px)`, opacity: i === 0 ? 0 : 1 })), { duration: o.duration, easing: 'linear' });
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
name: 'gravity-text',
|
|
350
|
+
kind: 'text',
|
|
351
|
+
description: 'Each character drops in under gravity with a bounce, staggered (splits the text into aria-hidden spans; the label stays readable).',
|
|
352
|
+
defaults: { height: 60, bounce: 0.45, duration: 900, stagger: 45 },
|
|
353
|
+
run: (el, o, ctx) => {
|
|
354
|
+
if (ctx.reduced)
|
|
355
|
+
return;
|
|
356
|
+
if (!el.dataset.usaSplit) {
|
|
357
|
+
const text = el.textContent || '';
|
|
358
|
+
el.setAttribute('aria-label', text);
|
|
359
|
+
el.textContent = '';
|
|
360
|
+
for (const ch of Array.from(text)) {
|
|
361
|
+
const s = document.createElement('span');
|
|
362
|
+
s.setAttribute('aria-hidden', 'true');
|
|
363
|
+
s.style.display = 'inline-block';
|
|
364
|
+
s.style.whiteSpace = 'pre';
|
|
365
|
+
s.textContent = ch;
|
|
366
|
+
el.appendChild(s);
|
|
367
|
+
}
|
|
368
|
+
el.dataset.usaSplit = '1';
|
|
369
|
+
}
|
|
370
|
+
const hs = bounceKeyframes(o.bounce, 32);
|
|
371
|
+
return all(Array.from(el.children).map((s, i) => ctx.animate(s, hs.map((h) => ({ transform: `translateY(${(-h * o.height).toFixed(1)}px)` })), { duration: o.duration, delay: i * o.stagger, easing: 'linear', fill: 'backwards' })));
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
name: 'spring-follow',
|
|
376
|
+
kind: 'cursor',
|
|
377
|
+
description: 'The element springs toward the pointer while it moves over its parent, and back home on leave (persistent).',
|
|
378
|
+
defaults: { stiffness: 0.12, damping: 0.75, range: 40 },
|
|
379
|
+
run: (el, o, ctx) => {
|
|
380
|
+
if (ctx.reduced)
|
|
381
|
+
return;
|
|
382
|
+
const host = el.parentElement || el;
|
|
383
|
+
let tx = 0;
|
|
384
|
+
let ty = 0;
|
|
385
|
+
let x = 0;
|
|
386
|
+
let y = 0;
|
|
387
|
+
let vx = 0;
|
|
388
|
+
let vy = 0;
|
|
389
|
+
let raf = 0;
|
|
390
|
+
const tick = () => {
|
|
391
|
+
vx = (vx + (tx - x) * o.stiffness) * o.damping;
|
|
392
|
+
vy = (vy + (ty - y) * o.stiffness) * o.damping;
|
|
393
|
+
x += vx;
|
|
394
|
+
y += vy;
|
|
395
|
+
el.style.translate = `${x.toFixed(2)}px ${y.toFixed(2)}px`;
|
|
396
|
+
raf = Math.abs(vx) + Math.abs(vy) + Math.abs(tx - x) + Math.abs(ty - y) > 0.05 ? requestAnimationFrame(tick) : 0;
|
|
397
|
+
};
|
|
398
|
+
const kick = () => {
|
|
399
|
+
if (!raf)
|
|
400
|
+
raf = requestAnimationFrame(tick);
|
|
401
|
+
};
|
|
402
|
+
const move = (e) => {
|
|
403
|
+
const r = host.getBoundingClientRect();
|
|
404
|
+
const nx = (e.clientX - r.left) / (r.width || 1) - 0.5;
|
|
405
|
+
const ny = (e.clientY - r.top) / (r.height || 1) - 0.5;
|
|
406
|
+
tx = nx * 2 * o.range;
|
|
407
|
+
ty = ny * 2 * o.range;
|
|
408
|
+
kick();
|
|
409
|
+
};
|
|
410
|
+
const leave = () => {
|
|
411
|
+
tx = ty = 0;
|
|
412
|
+
kick();
|
|
413
|
+
};
|
|
414
|
+
host.addEventListener('pointermove', move);
|
|
415
|
+
host.addEventListener('pointerleave', leave);
|
|
416
|
+
return () => {
|
|
417
|
+
host.removeEventListener('pointermove', move);
|
|
418
|
+
host.removeEventListener('pointerleave', leave);
|
|
419
|
+
cancelAnimationFrame(raf);
|
|
420
|
+
el.style.translate = '';
|
|
421
|
+
};
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
name: 'bell-swing',
|
|
426
|
+
kind: 'attention',
|
|
427
|
+
description: 'Swings from its top like a ringing bell with damped oscillation.',
|
|
428
|
+
defaults: { angle: 22, stiffness: 120, damping: 4 },
|
|
429
|
+
run: (el, o, ctx) => {
|
|
430
|
+
if (ctx.reduced)
|
|
431
|
+
return;
|
|
432
|
+
el.style.transformOrigin = 'top center';
|
|
433
|
+
const { frames, duration } = springKeyframes((p) => ({ transform: `rotate(${(o.angle * (1 - p)).toFixed(2)}deg)` }), { stiffness: o.stiffness, damping: o.damping, steps: 48 });
|
|
434
|
+
frames[0] = { transform: 'rotate(0deg)' };
|
|
435
|
+
return ctx.animate(el, frames, { duration: Math.min(duration, 2400), easing: 'linear' });
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
];
|
|
439
|
+
|
|
440
|
+
/** A full-viewport, aria-hidden, pointer-blocking (while covering) transition layer. */
|
|
441
|
+
function screen(css = '') {
|
|
442
|
+
const s = document.createElement('div');
|
|
443
|
+
s.setAttribute('aria-hidden', 'true');
|
|
444
|
+
s.setAttribute('data-usa-page-fx', '');
|
|
445
|
+
s.style.cssText = `position:fixed;inset:0;z-index:2147483001;pointer-events:auto;${css}`;
|
|
446
|
+
document.body.appendChild(s);
|
|
447
|
+
return s;
|
|
448
|
+
}
|
|
449
|
+
const wait = (a) => (a ? a.finished.catch(() => undefined) : Promise.resolve());
|
|
450
|
+
const call = async (fn) => {
|
|
451
|
+
if (typeof fn === 'function')
|
|
452
|
+
await fn();
|
|
453
|
+
};
|
|
454
|
+
/** Reduced-motion version of every transition: a quick cross-fade through the cover colour. */
|
|
455
|
+
async function crossFade(o, ctx) {
|
|
456
|
+
const s = screen(`background:${o.color};opacity:0`);
|
|
457
|
+
await wait(ctx.animate(s, [{ opacity: 0 }, { opacity: 1 }], { duration: 150, fill: 'forwards' }));
|
|
458
|
+
await call(o.onCovered);
|
|
459
|
+
await wait(ctx.animate(s, [{ opacity: 1 }, { opacity: 0 }], { duration: 150, fill: 'forwards' }));
|
|
460
|
+
s.remove();
|
|
461
|
+
}
|
|
462
|
+
/** Build a transition from a cover layer and cover / reveal animations of its parts. */
|
|
463
|
+
function transition(build) {
|
|
464
|
+
return async (el, o, ctx) => {
|
|
465
|
+
if (ctx.reduced)
|
|
466
|
+
return crossFade(o, ctx);
|
|
467
|
+
const s = screen('pointer-events:auto');
|
|
468
|
+
const { parts, cover, delay } = build(s, o, ctx, el);
|
|
469
|
+
const frames = (i) => (typeof cover === 'function' ? cover(i) : cover);
|
|
470
|
+
const half = o.duration / 2;
|
|
471
|
+
await Promise.all(parts.map((p, i) => wait(ctx.animate(p, frames(i), { duration: half, delay: delay?.(i) ?? 0, easing: 'cubic-bezier(0.65, 0, 0.35, 1)', fill: 'both' }))));
|
|
472
|
+
await call(o.onCovered);
|
|
473
|
+
if (o.hold)
|
|
474
|
+
await new Promise((r) => setTimeout(r, o.hold));
|
|
475
|
+
await Promise.all(parts.map((p, i) => wait(ctx.animate(p, [...frames(i)].reverse(), { duration: half, delay: delay?.(i) ?? 0, easing: 'cubic-bezier(0.65, 0, 0.35, 1)', fill: 'both' }))));
|
|
476
|
+
s.remove();
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
const part = (s, css) => {
|
|
480
|
+
const p = document.createElement('div');
|
|
481
|
+
p.style.cssText = `position:absolute;${css}`;
|
|
482
|
+
s.appendChild(p);
|
|
483
|
+
return p;
|
|
484
|
+
};
|
|
485
|
+
const TRANSITION_DEFAULTS = { color: '#0f0f1a', duration: 1000, hold: 0, onCovered: undefined };
|
|
486
|
+
const PAGE_FX = [
|
|
487
|
+
{
|
|
488
|
+
name: 'curtain',
|
|
489
|
+
kind: 'page',
|
|
490
|
+
description: 'Two curtain panels close from the sides, call onCovered(), then open.',
|
|
491
|
+
reduced: 'run',
|
|
492
|
+
defaults: TRANSITION_DEFAULTS,
|
|
493
|
+
run: transition((s, o) => ({
|
|
494
|
+
parts: [part(s, `top:0;bottom:0;left:0;width:50.5%;background:${o.color}`), part(s, `top:0;bottom:0;right:0;width:50.5%;background:${o.color}`)],
|
|
495
|
+
cover: (i) => [{ transform: `translateX(${i ? 100 : -100}%)` }, { transform: 'translateX(0)' }],
|
|
496
|
+
})),
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
name: 'iris',
|
|
500
|
+
kind: 'page',
|
|
501
|
+
description: 'A circle closes on the click point (or the element’s center), calls onCovered(), then opens.',
|
|
502
|
+
reduced: 'run',
|
|
503
|
+
defaults: TRANSITION_DEFAULTS,
|
|
504
|
+
run: transition((s, o, ctx, el) => {
|
|
505
|
+
const { x, y } = origin(el, ctx);
|
|
506
|
+
const r = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y)) + 2;
|
|
507
|
+
// a huge box-shadow ring around a hole: the hole shrinks to 0 to cover
|
|
508
|
+
const p = part(s, `left:${x}px;top:${y}px;width:0;height:0;border-radius:50%;box-shadow:0 0 0 ${Math.ceil(r)}px ${o.color}`);
|
|
509
|
+
s.style.background = 'transparent';
|
|
510
|
+
return { parts: [p], cover: [{ width: `${r * 2}px`, height: `${r * 2}px`, margin: `${-r}px 0 0 ${-r}px` }, { width: '0px', height: '0px', margin: '0px 0 0 0px' }] };
|
|
511
|
+
}),
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
name: 'pixel-dissolve',
|
|
515
|
+
kind: 'page',
|
|
516
|
+
description: 'The screen fills with pixels in random order, calls onCovered(), then dissolves (`cols` × `rows`).',
|
|
517
|
+
reduced: 'run',
|
|
518
|
+
defaults: { ...TRANSITION_DEFAULTS, cols: 16, rows: 10 },
|
|
519
|
+
run: transition((s, o) => {
|
|
520
|
+
const n = o.cols * o.rows;
|
|
521
|
+
const order = Array.from({ length: n }, (_, i) => i).sort(() => Math.random() - 0.5);
|
|
522
|
+
const parts = Array.from({ length: n }, (_, i) => part(s, `left:${((i % o.cols) * 100) / o.cols}%;top:${(Math.floor(i / o.cols) * 100) / o.rows}%;width:${100 / o.cols + 0.2}%;height:${100 / o.rows + 0.2}%;background:${o.color};opacity:0`));
|
|
523
|
+
return { parts, cover: [{ opacity: 0 }, { opacity: 1 }], delay: (i) => (order[i] / n) * (o.duration / 2) * 0.6 };
|
|
524
|
+
}),
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
name: 'blinds',
|
|
528
|
+
kind: 'page',
|
|
529
|
+
description: 'Horizontal slats rotate shut like venetian blinds, call onCovered(), then open (`slats`).',
|
|
530
|
+
reduced: 'run',
|
|
531
|
+
defaults: { ...TRANSITION_DEFAULTS, slats: 8 },
|
|
532
|
+
run: transition((s, o) => ({
|
|
533
|
+
parts: Array.from({ length: o.slats }, (_, i) => part(s, `left:0;right:0;top:${(i * 100) / o.slats}%;height:${100 / o.slats + 0.3}%;background:${o.color};transform-origin:top`)),
|
|
534
|
+
cover: [{ transform: 'scaleY(0)' }, { transform: 'scaleY(1)' }],
|
|
535
|
+
delay: (i) => i * 30,
|
|
536
|
+
})),
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
name: 'velocity-skew',
|
|
540
|
+
kind: 'scroll',
|
|
541
|
+
description: 'Skews the element with scroll velocity and eases back when scrolling stops (persistent).',
|
|
542
|
+
defaults: { max: 8, factor: 0.25 },
|
|
543
|
+
run: (el, o, ctx) => {
|
|
544
|
+
if (ctx.reduced)
|
|
545
|
+
return;
|
|
546
|
+
let last = scrollY;
|
|
547
|
+
let skew = 0;
|
|
548
|
+
let raf = 0;
|
|
549
|
+
const tick = () => {
|
|
550
|
+
const y = scrollY;
|
|
551
|
+
const target = Math.max(-o.max, Math.min(o.max, (y - last) * o.factor));
|
|
552
|
+
last = y;
|
|
553
|
+
skew += (target - skew) * 0.2;
|
|
554
|
+
el.style.transform = `skewY(${skew.toFixed(3)}deg)`;
|
|
555
|
+
raf = Math.abs(skew) > 0.01 || Math.abs(target) > 0.01 ? requestAnimationFrame(tick) : 0;
|
|
556
|
+
if (!raf)
|
|
557
|
+
el.style.transform = '';
|
|
558
|
+
};
|
|
559
|
+
const onScroll = () => {
|
|
560
|
+
if (!raf)
|
|
561
|
+
raf = requestAnimationFrame(tick);
|
|
562
|
+
};
|
|
563
|
+
addEventListener('scroll', onScroll, { passive: true });
|
|
564
|
+
return () => {
|
|
565
|
+
removeEventListener('scroll', onScroll);
|
|
566
|
+
cancelAnimationFrame(raf);
|
|
567
|
+
el.style.transform = '';
|
|
568
|
+
};
|
|
569
|
+
},
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
name: 'spotlight',
|
|
573
|
+
kind: 'cursor',
|
|
574
|
+
description: 'Dims the page except a soft circle that follows the pointer (persistent; `radius`, `dim`).',
|
|
575
|
+
defaults: { radius: 180, dim: 0.72 },
|
|
576
|
+
run: (_el, o, ctx) => {
|
|
577
|
+
if (ctx.reduced)
|
|
578
|
+
return;
|
|
579
|
+
const s = screen(`pointer-events:none;background:radial-gradient(circle ${o.radius}px at var(--x,50%) var(--y,50%),transparent 0,transparent 60%,rgba(0,0,0,${o.dim}) 100%)`);
|
|
580
|
+
const move = (e) => {
|
|
581
|
+
s.style.setProperty('--x', `${e.clientX}px`);
|
|
582
|
+
s.style.setProperty('--y', `${e.clientY}px`);
|
|
583
|
+
};
|
|
584
|
+
addEventListener('pointermove', move, { passive: true });
|
|
585
|
+
return () => {
|
|
586
|
+
removeEventListener('pointermove', move);
|
|
587
|
+
s.remove();
|
|
588
|
+
};
|
|
589
|
+
},
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
name: 'edge-glow',
|
|
593
|
+
kind: 'scroll',
|
|
594
|
+
description: 'A soft glow lights the top / bottom edge of the viewport while scrolling in that direction (persistent).',
|
|
595
|
+
defaults: { color: '#7c5cff', size: 90 },
|
|
596
|
+
run: (_el, o, ctx) => {
|
|
597
|
+
if (ctx.reduced)
|
|
598
|
+
return;
|
|
599
|
+
const s = screen('pointer-events:none;opacity:0;transition:opacity .35s ease-out');
|
|
600
|
+
let last = scrollY;
|
|
601
|
+
let t = 0;
|
|
602
|
+
const onScroll = () => {
|
|
603
|
+
const down = scrollY >= last;
|
|
604
|
+
last = scrollY;
|
|
605
|
+
s.style.background = `linear-gradient(${down ? 'to top' : 'to bottom'},${o.color}55,transparent ${o.size}px)`;
|
|
606
|
+
s.style.opacity = '1';
|
|
607
|
+
clearTimeout(t);
|
|
608
|
+
t = setTimeout(() => (s.style.opacity = '0'), 160);
|
|
609
|
+
};
|
|
610
|
+
addEventListener('scroll', onScroll, { passive: true });
|
|
611
|
+
return () => {
|
|
612
|
+
removeEventListener('scroll', onScroll);
|
|
613
|
+
clearTimeout(t);
|
|
614
|
+
s.remove();
|
|
615
|
+
};
|
|
616
|
+
},
|
|
617
|
+
},
|
|
618
|
+
];
|
|
619
|
+
|
|
230
620
|
/**
|
|
231
621
|
* use-scroll-animate/components/effects — the 5.x effect packs, all
|
|
232
622
|
* registered through `registerEffect()` (5.0) and playable with
|
|
@@ -242,11 +632,21 @@ const CLICK_FX = [
|
|
|
242
632
|
/** The effect packs by version, in release order. */
|
|
243
633
|
const EFFECT_PACKS = {
|
|
244
634
|
'cards-click': [...CARD_FX, ...CLICK_FX],
|
|
635
|
+
physics: PHYSICS_FX,
|
|
636
|
+
page: PAGE_FX,
|
|
245
637
|
};
|
|
246
638
|
/** 5.1: card & click effects 2.0. */
|
|
247
639
|
function registerCardClickEffects() {
|
|
248
640
|
registerEffects(EFFECT_PACKS['cards-click']);
|
|
249
641
|
}
|
|
642
|
+
/** 5.2: bounce & physics micro-interactions. */
|
|
643
|
+
function registerPhysicsEffects() {
|
|
644
|
+
registerEffects(EFFECT_PACKS.physics);
|
|
645
|
+
}
|
|
646
|
+
/** 5.3: page-wide transitions and effects. */
|
|
647
|
+
function registerPageEffects() {
|
|
648
|
+
registerEffects(EFFECT_PACKS.page);
|
|
649
|
+
}
|
|
250
650
|
/** Register the built-ins and every pack (idempotent). */
|
|
251
651
|
function registerAllEffects() {
|
|
252
652
|
registerBuiltinEffects();
|
|
@@ -254,5 +654,5 @@ function registerAllEffects() {
|
|
|
254
654
|
registerEffects(defs);
|
|
255
655
|
}
|
|
256
656
|
|
|
257
|
-
export { CARD_FX, CLICK_FX, EFFECT_PACKS, fxLayer, registerAllEffects, registerCardClickEffects };
|
|
657
|
+
export { CARD_FX, CLICK_FX, EFFECT_PACKS, PAGE_FX, PHYSICS_FX, bounceKeyframes, fxLayer, registerAllEffects, registerCardClickEffects, registerPageEffects, registerPhysicsEffects, solveSpring, springKeyframes };
|
|
258
658
|
//# sourceMappingURL=effects.js.map
|