use-scroll-animate 5.6.0 → 5.7.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 +11 -0
- package/dist/components/effects.cjs +395 -0
- package/dist/components/effects.cjs.map +1 -1
- package/dist/components/effects.d.cts +75 -2
- package/dist/components/effects.d.ts +75 -2
- package/dist/components/effects.js +389 -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 +1 -1
- package/docs/components.md +16 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [5.7.0] - 2026-10-08
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Cursor pack** (`use-scroll-animate/components/effects`, `registerCursorEffects()`, `CURSOR_FX`, kind `cursor`, persistent, scoped to the bound element): `comet-trail` (`color`, `width`, `life`), `ribbon-trail` (rainbow, `width`, `life`), `sparkle-trail` (`colors`, `spacing`, `size`), `magnetic-dots` (dot grid behind the content leaning toward the pointer; `gap`, `radius`, `color`), `spotlight-cursor` (eased soft light; `color`, `size`, `ease`). Trails draw on one fixed, pointer-transparent overlay canvas that only animates while a tail is fading. Mouse / pen only by default — `touch: true` opts touch in.
|
|
14
|
+
- **Gestures → effects**: `bindGesture(el, 'fling' | 'twist' | 'long-press', effectNameOrCallback, { velocity, angle, duration, tolerance, effectOptions })` — fling = fast release (px/ms, with direction), twist = two-finger rotation past `angle`° (`cw` / `ccw`), long press charges `--usa-charge` 0 → 1 (`data-charging` while charging) and fires when full; moving cancels. Every fire dispatches `usa-gesture`. Pure helpers `flingVelocity()`, `angleDelta()`.
|
|
15
|
+
- `<usa-gesture-fx gesture effect options velocity angle duration [self]>` — plays the effect on its first child.
|
|
16
|
+
- Showcase: **Cursor trails** and **Gesture triggers** cards.
|
|
17
|
+
|
|
18
|
+
### Accessibility
|
|
19
|
+
- Cursor effects are skipped under reduced motion; gesture-fired effects go through `playEffect()`, which applies reduced motion. Gestures only add effects — they never replace a click / keyboard action.
|
|
20
|
+
|
|
10
21
|
## [5.6.0] - 2026-10-08
|
|
11
22
|
|
|
12
23
|
### Added
|
|
@@ -1441,6 +1441,388 @@ function defineAudio(tag = 'usa-audio') {
|
|
|
1441
1441
|
}, { id: 'usa-audio', text: 'usa-audio{display:block}usa-audio[data-audio-error] [data-audio-toggle]{outline:2px solid #ff5c8a}' });
|
|
1442
1442
|
}
|
|
1443
1443
|
|
|
1444
|
+
const touchOk = (e, o) => o.touch || e.pointerType !== 'touch';
|
|
1445
|
+
/** A fixed, viewport-sized canvas in the fx layer, redrawn while `draw` returns true. */
|
|
1446
|
+
function overlayCanvas(draw) {
|
|
1447
|
+
const c = document.createElement('canvas');
|
|
1448
|
+
c.style.cssText = 'position:absolute;inset:0;width:100%;height:100%';
|
|
1449
|
+
fxLayer().appendChild(c);
|
|
1450
|
+
const g = c.getContext('2d');
|
|
1451
|
+
let raf = 0;
|
|
1452
|
+
const frame = (now) => {
|
|
1453
|
+
raf = 0;
|
|
1454
|
+
if (!g)
|
|
1455
|
+
return;
|
|
1456
|
+
const w = innerWidth;
|
|
1457
|
+
const h = innerHeight;
|
|
1458
|
+
const dpr = Math.min(2, devicePixelRatio || 1);
|
|
1459
|
+
if (c.width !== Math.round(w * dpr) || c.height !== Math.round(h * dpr)) {
|
|
1460
|
+
c.width = Math.round(w * dpr);
|
|
1461
|
+
c.height = Math.round(h * dpr);
|
|
1462
|
+
}
|
|
1463
|
+
g.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
1464
|
+
g.clearRect(0, 0, w, h);
|
|
1465
|
+
if (draw(g, w, h, now))
|
|
1466
|
+
raf = requestAnimationFrame(frame);
|
|
1467
|
+
};
|
|
1468
|
+
return {
|
|
1469
|
+
kick: () => {
|
|
1470
|
+
if (!raf && g)
|
|
1471
|
+
raf = requestAnimationFrame(frame);
|
|
1472
|
+
},
|
|
1473
|
+
stop: () => {
|
|
1474
|
+
cancelAnimationFrame(raf);
|
|
1475
|
+
raf = 0;
|
|
1476
|
+
c.remove();
|
|
1477
|
+
},
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
/** Pointer trail on `el`: keeps the last `life` ms of points and redraws them. */
|
|
1481
|
+
function trail(el, o, paint) {
|
|
1482
|
+
const pts = [];
|
|
1483
|
+
const oc = overlayCanvas((g, _w, _h, now) => {
|
|
1484
|
+
while (pts.length && now - pts[0].t > o.life)
|
|
1485
|
+
pts.shift();
|
|
1486
|
+
if (pts.length > 1)
|
|
1487
|
+
paint(g, pts, now);
|
|
1488
|
+
return pts.length > 0;
|
|
1489
|
+
});
|
|
1490
|
+
const move = (e) => {
|
|
1491
|
+
if (!touchOk(e, o))
|
|
1492
|
+
return;
|
|
1493
|
+
pts.push({ x: e.clientX, y: e.clientY, t: performance.now() });
|
|
1494
|
+
if (pts.length > 64)
|
|
1495
|
+
pts.shift();
|
|
1496
|
+
oc.kick();
|
|
1497
|
+
};
|
|
1498
|
+
el.addEventListener('pointermove', move, { passive: true });
|
|
1499
|
+
return () => {
|
|
1500
|
+
el.removeEventListener('pointermove', move);
|
|
1501
|
+
oc.stop();
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
const CURSOR_FX = [
|
|
1505
|
+
{
|
|
1506
|
+
name: 'comet-trail',
|
|
1507
|
+
kind: 'cursor',
|
|
1508
|
+
description: 'A glowing comet tail follows the pointer over the element (persistent; `color`, `width`, `life`).',
|
|
1509
|
+
defaults: { color: '#7c5cff', width: 10, life: 320, touch: false },
|
|
1510
|
+
run: (el, o) => trail(el, o, (g, pts, now) => {
|
|
1511
|
+
const [r, gg, b] = hexRgb(o.color);
|
|
1512
|
+
g.lineCap = 'round';
|
|
1513
|
+
for (let i = 1; i < pts.length; i++) {
|
|
1514
|
+
const k = 1 - (now - pts[i].t) / o.life;
|
|
1515
|
+
g.strokeStyle = `rgba(${r},${gg},${b},${(k * 0.9).toFixed(3)})`;
|
|
1516
|
+
g.lineWidth = Math.max(0.5, o.width * k);
|
|
1517
|
+
g.beginPath();
|
|
1518
|
+
g.moveTo(pts[i - 1].x, pts[i - 1].y);
|
|
1519
|
+
g.lineTo(pts[i].x, pts[i].y);
|
|
1520
|
+
g.stroke();
|
|
1521
|
+
}
|
|
1522
|
+
}),
|
|
1523
|
+
},
|
|
1524
|
+
{
|
|
1525
|
+
name: 'ribbon-trail',
|
|
1526
|
+
kind: 'cursor',
|
|
1527
|
+
description: 'A smooth rainbow ribbon flows behind the pointer (persistent; `width`, `life`).',
|
|
1528
|
+
defaults: { width: 18, life: 600, touch: false },
|
|
1529
|
+
run: (el, o) => trail(el, o, (g, pts, now) => {
|
|
1530
|
+
for (let i = 2; i < pts.length; i++) {
|
|
1531
|
+
const k = 1 - (now - pts[i].t) / o.life;
|
|
1532
|
+
const a = pts[i - 2];
|
|
1533
|
+
const m = pts[i - 1];
|
|
1534
|
+
const b = pts[i];
|
|
1535
|
+
g.strokeStyle = `hsla(${(pts[i].t / 6) % 360},90%,62%,${(k * 0.85).toFixed(3)})`;
|
|
1536
|
+
g.lineWidth = Math.max(0.5, o.width * k * Math.sin(Math.PI * Math.min(1, (i / pts.length) * 1.1)));
|
|
1537
|
+
g.lineCap = 'round';
|
|
1538
|
+
g.beginPath();
|
|
1539
|
+
g.moveTo((a.x + m.x) / 2, (a.y + m.y) / 2);
|
|
1540
|
+
g.quadraticCurveTo(m.x, m.y, (m.x + b.x) / 2, (m.y + b.y) / 2);
|
|
1541
|
+
g.stroke();
|
|
1542
|
+
}
|
|
1543
|
+
}),
|
|
1544
|
+
},
|
|
1545
|
+
{
|
|
1546
|
+
name: 'sparkle-trail',
|
|
1547
|
+
kind: 'cursor',
|
|
1548
|
+
description: 'Little stars twinkle off the pointer as it moves (persistent; `colors`, `spacing` px between stars).',
|
|
1549
|
+
defaults: { colors: PALETTE, spacing: 14, size: 14, touch: false },
|
|
1550
|
+
run: (el, o, ctx) => {
|
|
1551
|
+
let lx = -1e4;
|
|
1552
|
+
let ly = -1e4;
|
|
1553
|
+
const move = (e) => {
|
|
1554
|
+
if (!touchOk(e, o) || Math.hypot(e.clientX - lx, e.clientY - ly) < o.spacing)
|
|
1555
|
+
return;
|
|
1556
|
+
lx = e.clientX;
|
|
1557
|
+
ly = e.clientY;
|
|
1558
|
+
const s = o.size * rand(0.6, 1.2);
|
|
1559
|
+
spawn(e.clientX - s / 2, e.clientY - s / 2, `font-size:${s}px;line-height:1;color:${o.colors[Math.floor(rand(0, o.colors.length))]}`, ctx, [{ transform: 'translate(0,0) scale(0) rotate(0deg)', opacity: 1 }, { transform: `translate(${rand(-14, 14)}px,${rand(4, 26)}px) scale(1) rotate(${rand(-90, 90)}deg)`, opacity: 0 }], { duration: rand(500, 800), easing: 'ease-out' }, '✦');
|
|
1560
|
+
};
|
|
1561
|
+
el.addEventListener('pointermove', move, { passive: true });
|
|
1562
|
+
return () => el.removeEventListener('pointermove', move);
|
|
1563
|
+
},
|
|
1564
|
+
},
|
|
1565
|
+
{
|
|
1566
|
+
name: 'magnetic-dots',
|
|
1567
|
+
kind: 'cursor',
|
|
1568
|
+
description: 'A grid of dots behind the content leans toward the pointer like iron filings to a magnet (persistent; `gap`, `radius`, `color`).',
|
|
1569
|
+
defaults: { gap: 22, radius: 120, color: '#7c5cff', background: 'transparent', touch: false },
|
|
1570
|
+
reduced: 'skip',
|
|
1571
|
+
run: (el, o, ctx) => {
|
|
1572
|
+
const p = { x: -1e4, y: -1e4 };
|
|
1573
|
+
const move = (e) => {
|
|
1574
|
+
if (!touchOk(e, o))
|
|
1575
|
+
return;
|
|
1576
|
+
const r = el.getBoundingClientRect();
|
|
1577
|
+
p.x = e.clientX - r.left;
|
|
1578
|
+
p.y = e.clientY - r.top;
|
|
1579
|
+
};
|
|
1580
|
+
const leave = () => ((p.x = -1e4), (p.y = -1e4));
|
|
1581
|
+
el.addEventListener('pointermove', move, { passive: true });
|
|
1582
|
+
el.addEventListener('pointerleave', leave);
|
|
1583
|
+
const off = canvasBackground(el, ctx, {
|
|
1584
|
+
draw: ({ ctx: g, w, h }) => {
|
|
1585
|
+
g.clearRect(0, 0, w, h);
|
|
1586
|
+
if (o.background !== 'transparent') {
|
|
1587
|
+
g.fillStyle = o.background;
|
|
1588
|
+
g.fillRect(0, 0, w, h);
|
|
1589
|
+
}
|
|
1590
|
+
g.fillStyle = o.color;
|
|
1591
|
+
for (let y = o.gap / 2; y < h; y += o.gap)
|
|
1592
|
+
for (let x = o.gap / 2; x < w; x += o.gap) {
|
|
1593
|
+
const dx = p.x - x;
|
|
1594
|
+
const dy = p.y - y;
|
|
1595
|
+
const d = Math.hypot(dx, dy);
|
|
1596
|
+
const k = d < o.radius ? (1 - d / o.radius) ** 2 : 0;
|
|
1597
|
+
const s = 1.5 + k * 3;
|
|
1598
|
+
g.globalAlpha = 0.35 + k * 0.65;
|
|
1599
|
+
g.fillRect(x + dx * k * 0.35 - s / 2, y + dy * k * 0.35 - s / 2, s, s);
|
|
1600
|
+
}
|
|
1601
|
+
g.globalAlpha = 1;
|
|
1602
|
+
},
|
|
1603
|
+
}, o);
|
|
1604
|
+
return () => {
|
|
1605
|
+
el.removeEventListener('pointermove', move);
|
|
1606
|
+
el.removeEventListener('pointerleave', leave);
|
|
1607
|
+
off();
|
|
1608
|
+
};
|
|
1609
|
+
},
|
|
1610
|
+
},
|
|
1611
|
+
{
|
|
1612
|
+
name: 'spotlight-cursor',
|
|
1613
|
+
kind: 'cursor',
|
|
1614
|
+
description: 'A soft light follows the pointer across the element, easing behind it (persistent; `color`, `size`).',
|
|
1615
|
+
defaults: { color: '#ffffff', size: 260, ease: 0.18, touch: false },
|
|
1616
|
+
run: (el, o) => {
|
|
1617
|
+
const [r, g, b] = hexRgb(o.color);
|
|
1618
|
+
const s = document.createElement('span');
|
|
1619
|
+
s.setAttribute('aria-hidden', 'true');
|
|
1620
|
+
s.style.cssText = `position:absolute;inset:0;pointer-events:none;border-radius:inherit;opacity:0;transition:opacity .25s;mix-blend-mode:soft-light;background:radial-gradient(circle ${o.size / 2}px at var(--sx,50%) var(--sy,50%),rgba(${r},${g},${b},.75),transparent)`;
|
|
1621
|
+
const restore = el.style.position;
|
|
1622
|
+
if (getComputedStyle(el).position === 'static')
|
|
1623
|
+
el.style.position = 'relative';
|
|
1624
|
+
el.appendChild(s);
|
|
1625
|
+
let tx = 0;
|
|
1626
|
+
let ty = 0;
|
|
1627
|
+
let x = 0;
|
|
1628
|
+
let y = 0;
|
|
1629
|
+
let raf = 0;
|
|
1630
|
+
const tick = () => {
|
|
1631
|
+
x += (tx - x) * o.ease;
|
|
1632
|
+
y += (ty - y) * o.ease;
|
|
1633
|
+
s.style.setProperty('--sx', `${x.toFixed(1)}px`);
|
|
1634
|
+
s.style.setProperty('--sy', `${y.toFixed(1)}px`);
|
|
1635
|
+
raf = Math.abs(tx - x) + Math.abs(ty - y) > 0.3 ? requestAnimationFrame(tick) : 0;
|
|
1636
|
+
};
|
|
1637
|
+
const move = (e) => {
|
|
1638
|
+
if (!touchOk(e, o))
|
|
1639
|
+
return;
|
|
1640
|
+
const rc = el.getBoundingClientRect();
|
|
1641
|
+
tx = e.clientX - rc.left;
|
|
1642
|
+
ty = e.clientY - rc.top;
|
|
1643
|
+
if (s.style.opacity !== '1')
|
|
1644
|
+
((x = tx), (y = ty), (s.style.opacity = '1'));
|
|
1645
|
+
if (!raf)
|
|
1646
|
+
raf = requestAnimationFrame(tick);
|
|
1647
|
+
};
|
|
1648
|
+
const leave = () => (s.style.opacity = '0');
|
|
1649
|
+
el.addEventListener('pointermove', move, { passive: true });
|
|
1650
|
+
el.addEventListener('pointerleave', leave);
|
|
1651
|
+
return () => {
|
|
1652
|
+
cancelAnimationFrame(raf);
|
|
1653
|
+
el.removeEventListener('pointermove', move);
|
|
1654
|
+
el.removeEventListener('pointerleave', leave);
|
|
1655
|
+
s.remove();
|
|
1656
|
+
el.style.position = restore;
|
|
1657
|
+
};
|
|
1658
|
+
},
|
|
1659
|
+
},
|
|
1660
|
+
];
|
|
1661
|
+
// --- gestures → effects ------------------------------------------------------
|
|
1662
|
+
const GESTURES = ['fling', 'twist', 'long-press'];
|
|
1663
|
+
/** Release velocity (px/ms) from recent pointer samples: uses the last `window` ms (pure). */
|
|
1664
|
+
function flingVelocity(pts, window = 90) {
|
|
1665
|
+
if (pts.length < 2)
|
|
1666
|
+
return { vx: 0, vy: 0, speed: 0 };
|
|
1667
|
+
const last = pts[pts.length - 1];
|
|
1668
|
+
let first = pts[0];
|
|
1669
|
+
for (let i = pts.length - 2; i >= 0; i--) {
|
|
1670
|
+
first = pts[i];
|
|
1671
|
+
if (last.t - pts[i].t >= window)
|
|
1672
|
+
break;
|
|
1673
|
+
}
|
|
1674
|
+
const dt = Math.max(1, last.t - first.t);
|
|
1675
|
+
const vx = (last.x - first.x) / dt;
|
|
1676
|
+
const vy = (last.y - first.y) / dt;
|
|
1677
|
+
return { vx, vy, speed: Math.hypot(vx, vy) };
|
|
1678
|
+
}
|
|
1679
|
+
/** Signed smallest difference between two angles in degrees, in (-180, 180] (pure). */
|
|
1680
|
+
function angleDelta(a, b) {
|
|
1681
|
+
let d = (b - a) % 360;
|
|
1682
|
+
if (d > 180)
|
|
1683
|
+
d -= 360;
|
|
1684
|
+
if (d <= -180)
|
|
1685
|
+
d += 360;
|
|
1686
|
+
return d;
|
|
1687
|
+
}
|
|
1688
|
+
/**
|
|
1689
|
+
* Fire `effect` (a registered effect name, or a callback) when `gesture`
|
|
1690
|
+
* happens on `el`. Returns an unbind.
|
|
1691
|
+
*/
|
|
1692
|
+
function bindGesture(el, gesture, effect, o = {}) {
|
|
1693
|
+
const { velocity = 0.8, angle = 30, duration = 650, tolerance = 10, effectOptions = {} } = o;
|
|
1694
|
+
const pts = new Map();
|
|
1695
|
+
let path = [];
|
|
1696
|
+
let base = null;
|
|
1697
|
+
let press = null;
|
|
1698
|
+
const fire = (d, e) => {
|
|
1699
|
+
el.dispatchEvent(new CustomEvent('usa-gesture', { detail: d, bubbles: true }));
|
|
1700
|
+
if (typeof effect === 'function')
|
|
1701
|
+
effect(d, e);
|
|
1702
|
+
else
|
|
1703
|
+
components_fx.playEffect(el, effect, effectOptions, e).catch(() => undefined);
|
|
1704
|
+
};
|
|
1705
|
+
const twistAngle = () => {
|
|
1706
|
+
const [a, b] = [...pts.values()];
|
|
1707
|
+
return (Math.atan2(b.y - a.y, b.x - a.x) * 180) / Math.PI;
|
|
1708
|
+
};
|
|
1709
|
+
const charge = (k) => {
|
|
1710
|
+
el.style.setProperty('--usa-charge', k.toFixed(3));
|
|
1711
|
+
el.toggleAttribute('data-charging', k > 0 && k < 1);
|
|
1712
|
+
};
|
|
1713
|
+
const endPress = () => {
|
|
1714
|
+
if (!press)
|
|
1715
|
+
return;
|
|
1716
|
+
cancelAnimationFrame(press.raf);
|
|
1717
|
+
press = null;
|
|
1718
|
+
charge(0);
|
|
1719
|
+
};
|
|
1720
|
+
const down = (e) => {
|
|
1721
|
+
pts.set(e.pointerId ?? 1, { x: e.clientX, y: e.clientY });
|
|
1722
|
+
if (gesture === 'fling')
|
|
1723
|
+
path = [{ x: e.clientX, y: e.clientY, t: e.timeStamp || performance.now() }];
|
|
1724
|
+
if (gesture === 'twist' && pts.size === 2)
|
|
1725
|
+
base = twistAngle();
|
|
1726
|
+
if (gesture === 'long-press' && pts.size === 1) {
|
|
1727
|
+
press = { x: e.clientX, y: e.clientY, t0: performance.now(), raf: 0 };
|
|
1728
|
+
const step = (now) => {
|
|
1729
|
+
if (!press)
|
|
1730
|
+
return;
|
|
1731
|
+
const k = Math.min(1, (now - press.t0) / duration);
|
|
1732
|
+
charge(k);
|
|
1733
|
+
if (k >= 1) {
|
|
1734
|
+
endPress();
|
|
1735
|
+
el.style.setProperty('--usa-charge', '1');
|
|
1736
|
+
fire({ gesture, charge: 1 }, e);
|
|
1737
|
+
}
|
|
1738
|
+
else
|
|
1739
|
+
press.raf = requestAnimationFrame(step);
|
|
1740
|
+
};
|
|
1741
|
+
press.raf = requestAnimationFrame(step);
|
|
1742
|
+
}
|
|
1743
|
+
};
|
|
1744
|
+
const move = (e) => {
|
|
1745
|
+
if (!pts.has(e.pointerId ?? 1))
|
|
1746
|
+
return;
|
|
1747
|
+
pts.set(e.pointerId ?? 1, { x: e.clientX, y: e.clientY });
|
|
1748
|
+
if (gesture === 'fling') {
|
|
1749
|
+
path.push({ x: e.clientX, y: e.clientY, t: e.timeStamp || performance.now() });
|
|
1750
|
+
if (path.length > 20)
|
|
1751
|
+
path.shift();
|
|
1752
|
+
}
|
|
1753
|
+
else if (gesture === 'twist' && pts.size >= 2 && base !== null) {
|
|
1754
|
+
const now = twistAngle();
|
|
1755
|
+
const d = angleDelta(base, now);
|
|
1756
|
+
if (Math.abs(d) >= angle) {
|
|
1757
|
+
base = now;
|
|
1758
|
+
fire({ gesture, angle: d, direction: d > 0 ? 'cw' : 'ccw' }, e);
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
else if (gesture === 'long-press' && press && Math.hypot(e.clientX - press.x, e.clientY - press.y) > tolerance)
|
|
1762
|
+
endPress();
|
|
1763
|
+
};
|
|
1764
|
+
const up = (e) => {
|
|
1765
|
+
pts.delete(e.pointerId ?? 1);
|
|
1766
|
+
if (gesture === 'fling' && path.length) {
|
|
1767
|
+
path.push({ x: e.clientX, y: e.clientY, t: e.timeStamp || performance.now() });
|
|
1768
|
+
const v = flingVelocity(path);
|
|
1769
|
+
path = [];
|
|
1770
|
+
if (v.speed >= velocity) {
|
|
1771
|
+
const direction = Math.abs(v.vx) > Math.abs(v.vy) ? (v.vx > 0 ? 'right' : 'left') : v.vy > 0 ? 'down' : 'up';
|
|
1772
|
+
fire({ gesture, ...v, direction }, e);
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
if (pts.size < 2)
|
|
1776
|
+
base = null;
|
|
1777
|
+
endPress();
|
|
1778
|
+
};
|
|
1779
|
+
el.addEventListener('pointerdown', down);
|
|
1780
|
+
el.addEventListener('pointermove', move);
|
|
1781
|
+
el.addEventListener('pointerup', up);
|
|
1782
|
+
el.addEventListener('pointercancel', up);
|
|
1783
|
+
if (gesture === 'twist' && !el.style.touchAction)
|
|
1784
|
+
el.style.touchAction = 'none';
|
|
1785
|
+
return () => {
|
|
1786
|
+
endPress();
|
|
1787
|
+
el.removeEventListener('pointerdown', down);
|
|
1788
|
+
el.removeEventListener('pointermove', move);
|
|
1789
|
+
el.removeEventListener('pointerup', up);
|
|
1790
|
+
el.removeEventListener('pointercancel', up);
|
|
1791
|
+
};
|
|
1792
|
+
}
|
|
1793
|
+
/**
|
|
1794
|
+
* `<usa-gesture-fx gesture="fling | twist | long-press" effect="tada"
|
|
1795
|
+
* options='{"…"}' velocity angle duration>` — plays `effect` on its first
|
|
1796
|
+
* child (or itself with `self`) when the gesture happens.
|
|
1797
|
+
*/
|
|
1798
|
+
function defineGestureFx(tag = 'usa-gesture-fx') {
|
|
1799
|
+
return base.defineElement(tag, (Base) => class UsaGestureFx extends Base {
|
|
1800
|
+
static get observedAttributes() {
|
|
1801
|
+
return ['gesture', 'effect', 'options'];
|
|
1802
|
+
}
|
|
1803
|
+
get gesture() {
|
|
1804
|
+
const g = this.str('gesture', 'fling');
|
|
1805
|
+
return GESTURES.includes(g) ? g : 'fling';
|
|
1806
|
+
}
|
|
1807
|
+
mount() {
|
|
1808
|
+
let effectOptions = {};
|
|
1809
|
+
try {
|
|
1810
|
+
effectOptions = JSON.parse(this.str('options', '{}')) || {};
|
|
1811
|
+
}
|
|
1812
|
+
catch {
|
|
1813
|
+
/* ignore bad JSON */
|
|
1814
|
+
}
|
|
1815
|
+
const target = this.flag('self') ? this : this.firstElementChild || this;
|
|
1816
|
+
const effect = this.str('effect', 'pulse');
|
|
1817
|
+
this.onCleanup(bindGesture(this, this.gesture, (_d, e) => void components_fx.playEffect(target, effect, effectOptions, e).catch(() => undefined), {
|
|
1818
|
+
velocity: this.num('velocity', 0.8),
|
|
1819
|
+
angle: this.num('angle', 30),
|
|
1820
|
+
duration: this.num('duration', 650),
|
|
1821
|
+
}));
|
|
1822
|
+
}
|
|
1823
|
+
}, { id: 'usa-gesture-fx', text: 'usa-gesture-fx{display:inline-block;touch-action:none;user-select:none}' });
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1444
1826
|
/**
|
|
1445
1827
|
* use-scroll-animate/components/effects — the 5.x effect packs, all
|
|
1446
1828
|
* registered through `registerEffect()` (5.0) and playable with
|
|
@@ -1460,6 +1842,7 @@ const EFFECT_PACKS = {
|
|
|
1460
1842
|
page: PAGE_FX,
|
|
1461
1843
|
generative: GENERATIVE_FX,
|
|
1462
1844
|
audio: AUDIO_FX,
|
|
1845
|
+
cursor: CURSOR_FX,
|
|
1463
1846
|
};
|
|
1464
1847
|
/** 5.1: card & click effects 2.0. */
|
|
1465
1848
|
function registerCardClickEffects() {
|
|
@@ -1481,10 +1864,15 @@ function registerGenerativeEffects() {
|
|
|
1481
1864
|
function registerAudioEffects() {
|
|
1482
1865
|
components_fx.registerEffects(EFFECT_PACKS.audio);
|
|
1483
1866
|
}
|
|
1867
|
+
/** 5.7: cursor trails, magnetic dots, spotlight cursor. */
|
|
1868
|
+
function registerCursorEffects() {
|
|
1869
|
+
components_fx.registerEffects(EFFECT_PACKS.cursor);
|
|
1870
|
+
}
|
|
1484
1871
|
/** Define the 5.x elements of this entry (`<usa-story>`, …) under their default tags. */
|
|
1485
1872
|
function defineEffectElements() {
|
|
1486
1873
|
defineStory();
|
|
1487
1874
|
defineAudio();
|
|
1875
|
+
defineGestureFx();
|
|
1488
1876
|
}
|
|
1489
1877
|
/** Register the built-ins and every pack (idempotent). */
|
|
1490
1878
|
function registerAllEffects() {
|
|
@@ -1496,20 +1884,26 @@ function registerAllEffects() {
|
|
|
1496
1884
|
exports.AUDIO_FX = AUDIO_FX;
|
|
1497
1885
|
exports.CARD_FX = CARD_FX;
|
|
1498
1886
|
exports.CLICK_FX = CLICK_FX;
|
|
1887
|
+
exports.CURSOR_FX = CURSOR_FX;
|
|
1499
1888
|
exports.EFFECT_PACKS = EFFECT_PACKS;
|
|
1500
1889
|
exports.GENERATIVE_FX = GENERATIVE_FX;
|
|
1890
|
+
exports.GESTURES = GESTURES;
|
|
1501
1891
|
exports.PAGE_FX = PAGE_FX;
|
|
1502
1892
|
exports.PHYSICS_FX = PHYSICS_FX;
|
|
1503
1893
|
exports.STORY_TEMPLATES = STORY_TEMPLATES;
|
|
1894
|
+
exports.angleDelta = angleDelta;
|
|
1504
1895
|
exports.bindBeat = bindBeat;
|
|
1896
|
+
exports.bindGesture = bindGesture;
|
|
1505
1897
|
exports.bounceKeyframes = bounceKeyframes;
|
|
1506
1898
|
exports.canvasBackground = canvasBackground;
|
|
1507
1899
|
exports.createBeatDetector = createBeatDetector;
|
|
1508
1900
|
exports.defineAudio = defineAudio;
|
|
1509
1901
|
exports.defineEffectElements = defineEffectElements;
|
|
1902
|
+
exports.defineGestureFx = defineGestureFx;
|
|
1510
1903
|
exports.defineStory = defineStory;
|
|
1511
1904
|
exports.disableAudio = disableAudio;
|
|
1512
1905
|
exports.enableAudio = enableAudio;
|
|
1906
|
+
exports.flingVelocity = flingVelocity;
|
|
1513
1907
|
exports.formatCount = formatCount;
|
|
1514
1908
|
exports.fxLayer = fxLayer;
|
|
1515
1909
|
exports.getAudio = getAudio;
|
|
@@ -1519,6 +1913,7 @@ exports.onBeat = onBeat;
|
|
|
1519
1913
|
exports.registerAllEffects = registerAllEffects;
|
|
1520
1914
|
exports.registerAudioEffects = registerAudioEffects;
|
|
1521
1915
|
exports.registerCardClickEffects = registerCardClickEffects;
|
|
1916
|
+
exports.registerCursorEffects = registerCursorEffects;
|
|
1522
1917
|
exports.registerGenerativeEffects = registerGenerativeEffects;
|
|
1523
1918
|
exports.registerPageEffects = registerPageEffects;
|
|
1524
1919
|
exports.registerPhysicsEffects = registerPhysicsEffects;
|