@motion.page/sdk 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +372 -26
- package/dist/index.cjs +7 -7
- package/dist/index.cjs.map +5 -5
- package/dist/index.js +7 -7
- package/dist/index.js.map +5 -5
- package/dist/triggers/TriggerManager.d.ts +7 -0
- package/dist/utils/getLayoutRect.d.ts +8 -3
- package/package.json +1 -6
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
# @motion/sdk
|
|
1
|
+
# @motion.page/sdk
|
|
2
2
|
|
|
3
|
-
A high-performance
|
|
3
|
+
A high-performance animation SDK with a declarative API. Scroll-triggered animations, page transitions, custom cursors, gesture controls, text splitting, and more — zero runtime dependencies.
|
|
4
4
|
|
|
5
|
-
](https://www.npmjs.com/package/@motion.page/sdk)
|
|
6
|
+

|
|
7
|
+

|
|
6
8
|
|
|
7
9
|
---
|
|
8
10
|
|
|
@@ -30,11 +32,16 @@ A high-performance CSS animation SDK with a declarative, config-based API. Zero
|
|
|
30
32
|
## Installation
|
|
31
33
|
|
|
32
34
|
```bash
|
|
33
|
-
|
|
34
|
-
#
|
|
35
|
+
npm install @motion.page/sdk
|
|
36
|
+
# or
|
|
37
|
+
bun add @motion.page/sdk
|
|
38
|
+
# or
|
|
39
|
+
yarn add @motion.page/sdk
|
|
40
|
+
# or
|
|
41
|
+
pnpm add @motion.page/sdk
|
|
35
42
|
```
|
|
36
43
|
|
|
37
|
-
For direct browser use, see [Browser Build](#browser-build).
|
|
44
|
+
For direct browser use without a bundler, see [Browser Build](#browser-build).
|
|
38
45
|
|
|
39
46
|
---
|
|
40
47
|
|
|
@@ -43,7 +50,7 @@ For direct browser use, see [Browser Build](#browser-build).
|
|
|
43
50
|
### Basic Animation
|
|
44
51
|
|
|
45
52
|
```ts
|
|
46
|
-
import { Motion } from '@motion/sdk';
|
|
53
|
+
import { Motion } from '@motion.page/sdk';
|
|
47
54
|
|
|
48
55
|
// Fade in and slide up
|
|
49
56
|
Motion('hero-intro', '#hero', {
|
|
@@ -112,6 +119,22 @@ Motion('intro-sequence', [
|
|
|
112
119
|
Motion('hero-intro').restart();
|
|
113
120
|
```
|
|
114
121
|
|
|
122
|
+
### Object Animation
|
|
123
|
+
|
|
124
|
+
Plain JavaScript objects can be tweened — useful for canvas, audio, WebGL, or any non-DOM state:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
// Animate plain JS objects (useful for canvas, audio, WebGL)
|
|
128
|
+
const state = { volume: 0, brightness: 100 };
|
|
129
|
+
Motion('audio-fade', state, {
|
|
130
|
+
to: { volume: 1, brightness: 50 },
|
|
131
|
+
duration: 2,
|
|
132
|
+
onUpdate: () => {
|
|
133
|
+
audioNode.gain.value = state.volume;
|
|
134
|
+
},
|
|
135
|
+
}).play();
|
|
136
|
+
```
|
|
137
|
+
|
|
115
138
|
---
|
|
116
139
|
|
|
117
140
|
## Core Concept
|
|
@@ -120,6 +143,13 @@ Every animation in the SDK is a **named timeline**. The name is the first argume
|
|
|
120
143
|
|
|
121
144
|
Timelines are built declaratively via config objects; there are no `.to()` / `.from()` method calls. Animation state (targets, transforms, styles) is managed internally by the engine.
|
|
122
145
|
|
|
146
|
+
If `Motion('name', target, config)` is called when `'name'` already has a timeline, the new entries are **appended** to the existing timeline rather than replacing it. To rebuild from scratch, call `.kill()` first:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
Motion('hero').kill();
|
|
150
|
+
Motion('hero', '#hero', { from: { opacity: 0 }, to: { opacity: 1 }, duration: 0.8 }).onPageLoad();
|
|
151
|
+
```
|
|
152
|
+
|
|
123
153
|
---
|
|
124
154
|
|
|
125
155
|
## API Reference
|
|
@@ -149,7 +179,7 @@ Calling `Motion()` with the same name on an already-existing timeline returns it
|
|
|
149
179
|
|
|
150
180
|
| Method | Signature | Description |
|
|
151
181
|
|--------|-----------|-------------|
|
|
152
|
-
| `Motion.set` | `(target: TargetInput, vars: AnimationVars): void` | Immediately apply CSS / transform properties with no animation (
|
|
182
|
+
| `Motion.set` | `(target: TargetInput, vars: AnimationVars): void` | Immediately apply CSS / transform properties with no animation. Goes through the full animation engine pipeline (including color parsing and transform compositing) but completes in zero time, so applied values persist on the DOM. |
|
|
153
183
|
| `Motion.get` | `(name: string): Timeline \| undefined` | Get a timeline by name; returns `undefined` if none exists. |
|
|
154
184
|
| `Motion.has` | `(name: string): boolean` | Check whether a named timeline is registered. |
|
|
155
185
|
| `Motion.getNames` | `(): string[]` | Return the names of all registered timelines. |
|
|
@@ -184,6 +214,13 @@ Motion.reset('.animated-card');
|
|
|
184
214
|
|
|
185
215
|
GSAP-compatible utility functions accessible via `Motion.utils`. These are drop-in replacements for `gsap.utils.*` helpers.
|
|
186
216
|
|
|
217
|
+
`MotionUtils` is also exported directly from the package and can be imported independently:
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
import { MotionUtils } from '@motion.page/sdk';
|
|
221
|
+
// Same object as Motion.utils
|
|
222
|
+
```
|
|
223
|
+
|
|
187
224
|
| Method | Signature | Description |
|
|
188
225
|
|--------|-----------|-------------|
|
|
189
226
|
| `toArray` | `(target, scope?) → Element[]` | Convert CSS selector, NodeList, HTMLCollection, or Element to a flat array. Drop-in for `gsap.utils.toArray()`. |
|
|
@@ -297,6 +334,8 @@ tl.call(
|
|
|
297
334
|
| `"-=0.3"` | 0.3 s before the previous entry ends |
|
|
298
335
|
| `"<"` | At the same start time as the previous entry |
|
|
299
336
|
| `">"` | Immediately after the previous entry ends |
|
|
337
|
+
| `"<0.2"` | 0.2 s after the start of the previous entry |
|
|
338
|
+
| `">-0.1"` | 0.1 s before the end of the previous entry |
|
|
300
339
|
|
|
301
340
|
```ts
|
|
302
341
|
Motion('demo', '.box', { from: { opacity: 0 }, to: { opacity: 1 }, duration: 1 })
|
|
@@ -320,6 +359,22 @@ tl.clear(): this
|
|
|
320
359
|
|
|
321
360
|
All trigger methods are chainable and attach behaviour to the timeline without requiring you to manage event listeners manually.
|
|
322
361
|
|
|
362
|
+
#### Per-Element Triggers (`each: true`)
|
|
363
|
+
|
|
364
|
+
When targeting multiple elements, `each: true` creates independent per-element timeline instances. Without it, all matched elements share one timeline and play/reverse together.
|
|
365
|
+
|
|
366
|
+
```ts
|
|
367
|
+
// WITHOUT each — hovering any card plays ALL cards
|
|
368
|
+
Motion('card-hover', '.card', { to: { y: -8 }, duration: 0.3 })
|
|
369
|
+
.onHover({ onLeave: 'reverse' });
|
|
370
|
+
|
|
371
|
+
// WITH each — each card animates independently
|
|
372
|
+
Motion('card-hover', '.card', { to: { y: -8 }, duration: 0.3 })
|
|
373
|
+
.onHover({ each: true, onLeave: 'reverse' });
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
`each` is supported by `.onHover()`, `.onClick()`, `.onScroll()`, `.onMouseMove()`, and `.onGesture()`.
|
|
377
|
+
|
|
323
378
|
#### `.onHover(config?)`
|
|
324
379
|
|
|
325
380
|
Play on `mouseenter`, react on `mouseleave`.
|
|
@@ -375,12 +430,56 @@ interface ScrollConfig {
|
|
|
375
430
|
end?: string; // e.g. 'bottom 20%'
|
|
376
431
|
scrub?: boolean | number; // true = instant, number = smoothing seconds
|
|
377
432
|
snap?: number | number[] | ((progress: number) => number); // Snap scroll progress
|
|
378
|
-
markers?: boolean | MarkerConfig; // Debug markers
|
|
433
|
+
markers?: boolean | MarkerConfig; // Debug markers (pass object for styling)
|
|
379
434
|
scroller?: string | Element; // Custom scroll container
|
|
380
|
-
pin?: boolean | string; //
|
|
435
|
+
pin?: boolean | string; // true = pin animation target; string = pin a different element
|
|
381
436
|
pinSpacing?: boolean | 'margin' | 'padding';
|
|
382
437
|
each?: boolean;
|
|
383
|
-
toggleActions?: string; //
|
|
438
|
+
toggleActions?: string; // Format: 'onEnter onLeave onEnterBack onLeaveBack'
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
**`start` / `end` defaults:**
|
|
443
|
+
- Without `pin`: `start: 'top bottom'`, `end: 'bottom top'`
|
|
444
|
+
- With `pin`: `start: 'top top'`, `end: 'bottom top'`
|
|
445
|
+
|
|
446
|
+
**Relative `end` with `+=`:** When `end` starts with `+=`, the value is a distance measured from the `start` position:
|
|
447
|
+
|
|
448
|
+
```ts
|
|
449
|
+
.onScroll({ start: 'top center', end: '+=800' }) // 800px of scroll travel
|
|
450
|
+
.onScroll({ start: 'top top', end: '+=100vh' }) // one viewport height of scroll
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
**`pin: string`** pins a different element (e.g. a parent wrapper) while the animated child scrolls:
|
|
454
|
+
|
|
455
|
+
```ts
|
|
456
|
+
// Pin the parent section while the child content animates
|
|
457
|
+
Motion('content-reveal', '.content', {
|
|
458
|
+
from: { opacity: 0, y: 40 },
|
|
459
|
+
to: { opacity: 1, y: 0 },
|
|
460
|
+
duration: 1,
|
|
461
|
+
}).onScroll({ scrub: true, pin: '.section-wrapper', start: 'top top', end: '+=600' });
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
**`toggleActions`** controls what happens at each scroll boundary. Format: `"onEnter onLeave onEnterBack onLeaveBack"`. Default: `"play reverse play reverse"`. Valid actions: `play`, `pause`, `reverse`, `restart`, `reset`, `complete`, `kill`, `none`.
|
|
465
|
+
|
|
466
|
+
```ts
|
|
467
|
+
// Play once — never reverse (common for reveal animations)
|
|
468
|
+
.onScroll({ toggleActions: 'play none none none' });
|
|
469
|
+
|
|
470
|
+
// Re-animate every time it enters the viewport
|
|
471
|
+
.onScroll({ toggleActions: 'restart none none reset' });
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
**`markers`** accepts `true` for default debug markers, or a `MarkerConfig` object for custom styling:
|
|
475
|
+
|
|
476
|
+
```ts
|
|
477
|
+
interface MarkerConfig {
|
|
478
|
+
startColor?: string; // Default: 'green'
|
|
479
|
+
endColor?: string; // Default: 'red'
|
|
480
|
+
fontSize?: string; // e.g. '12px'
|
|
481
|
+
fontWeight?: string;
|
|
482
|
+
indent?: number; // Horizontal offset in pixels
|
|
384
483
|
}
|
|
385
484
|
```
|
|
386
485
|
|
|
@@ -428,6 +527,15 @@ interface MouseMoveConfig {
|
|
|
428
527
|
}
|
|
429
528
|
```
|
|
430
529
|
|
|
530
|
+
**Defaults:**
|
|
531
|
+
|
|
532
|
+
| Option | Default | Notes |
|
|
533
|
+
|--------|---------|-------|
|
|
534
|
+
| `type` | `'distance'` | |
|
|
535
|
+
| `startProgress` | `0.5` | Progress value when mouse is at rest |
|
|
536
|
+
| `leaveProgress` | `0.5` | Progress to animate to on mouse leave |
|
|
537
|
+
| `smooth` | `0.1` | `0` = instant tracking, `1` = maximum lag |
|
|
538
|
+
|
|
431
539
|
```ts
|
|
432
540
|
Motion('parallax-depth', '.layer', {
|
|
433
541
|
from: { x: -20, y: -20 },
|
|
@@ -438,7 +546,7 @@ Motion('parallax-depth', '.layer', {
|
|
|
438
546
|
|
|
439
547
|
#### `.onPageLoad()`
|
|
440
548
|
|
|
441
|
-
Play the animation automatically when the page finishes loading.
|
|
549
|
+
Play the animation automatically when the page finishes loading. If called after `DOMContentLoaded` has already fired (common in SPAs or scripts placed at the bottom of `<body>`), the animation plays immediately.
|
|
442
550
|
|
|
443
551
|
```ts
|
|
444
552
|
Motion('page-intro', [
|
|
@@ -447,6 +555,45 @@ Motion('page-intro', [
|
|
|
447
555
|
]).onPageLoad();
|
|
448
556
|
```
|
|
449
557
|
|
|
558
|
+
#### `.onPageExit(config?)`
|
|
559
|
+
|
|
560
|
+
Intercept link clicks, play the exit animation, then navigate to the destination URL after the timeline completes. Works on any website with no server-side dependencies.
|
|
561
|
+
|
|
562
|
+
```ts
|
|
563
|
+
interface PageExitConfig {
|
|
564
|
+
/** 'all' (default) | 'include' | 'exclude' */
|
|
565
|
+
mode?: 'all' | 'include' | 'exclude';
|
|
566
|
+
/** CSS selectors for links — required when mode is 'include' or 'exclude' */
|
|
567
|
+
selectors?: string;
|
|
568
|
+
/** Href patterns to skip automatically. Note: 'mailto' also skips tel: links. */
|
|
569
|
+
skipHref?: ('anchor' | 'javascript' | 'mailto')[];
|
|
570
|
+
}
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
- `mode: 'all'` (default) — intercepts every `<a>` on the page
|
|
574
|
+
- `mode: 'include'` — only links matching `selectors`
|
|
575
|
+
- `mode: 'exclude'` — all links except those matching `selectors`
|
|
576
|
+
- Automatically skips `target="_blank"` links and modifier-key clicks (Cmd/Ctrl/Shift/Alt)
|
|
577
|
+
|
|
578
|
+
```ts
|
|
579
|
+
// Fade-out on page exit — all links
|
|
580
|
+
Motion('page-exit', 'body', {
|
|
581
|
+
to: { opacity: 0 },
|
|
582
|
+
duration: 0.4,
|
|
583
|
+
ease: 'power2.in',
|
|
584
|
+
}).onPageExit();
|
|
585
|
+
|
|
586
|
+
// Only internal nav links
|
|
587
|
+
Motion('page-exit', 'body', {
|
|
588
|
+
to: { opacity: 0 },
|
|
589
|
+
duration: 0.4,
|
|
590
|
+
}).onPageExit({
|
|
591
|
+
mode: 'include',
|
|
592
|
+
selectors: 'nav a',
|
|
593
|
+
skipHref: ['anchor', 'mailto'],
|
|
594
|
+
});
|
|
595
|
+
```
|
|
596
|
+
|
|
450
597
|
#### `.onGesture(config)`
|
|
451
598
|
|
|
452
599
|
Respond to pointer, touch, wheel, or scroll gestures with fine-grained event-to-action mapping.
|
|
@@ -484,6 +631,33 @@ interface GestureConfig {
|
|
|
484
631
|
}
|
|
485
632
|
```
|
|
486
633
|
|
|
634
|
+
**Config defaults:**
|
|
635
|
+
|
|
636
|
+
| Option | Default | Unit | Notes |
|
|
637
|
+
|--------|---------|------|-------|
|
|
638
|
+
| `tolerance` | `1` | px | Min movement before direction events fire |
|
|
639
|
+
| `dragMinimum` | `10` | px | Distance before `Drag` fires |
|
|
640
|
+
| `wheelSpeed` | `1` | multiplier | Scales wheel delta |
|
|
641
|
+
| `scrollSpeed` | `1` | multiplier | Scales scroll delta |
|
|
642
|
+
| `stopDelay` | `150` | ms | Idle time after movement before `Stop` fires |
|
|
643
|
+
| `smooth` | `0` | 0–1 | Smoothness for `progressUp`/`progressDown` actions |
|
|
644
|
+
| `animationStep` | `0.1` | 0–1 | Progress step per event for `progressUp`/`progressDown` |
|
|
645
|
+
|
|
646
|
+
**Event distinctions:**
|
|
647
|
+
|
|
648
|
+
- `Up`/`Down`/`Left`/`Right` — fire **continuously** during movement
|
|
649
|
+
- `UpComplete`/`DownComplete`/etc. — fire **once on release** if that direction was active
|
|
650
|
+
- `PressInit` — fires immediately on press, before start position is recorded
|
|
651
|
+
- `Press` — fires after start position is recorded
|
|
652
|
+
- `Hover`/`HoverEnd` — require a `target` element (not the window)
|
|
653
|
+
- `playNext`/`playPrevious` actions — only work when `each: true` is set
|
|
654
|
+
|
|
655
|
+
`animationStep` can be a single number or a per-event map:
|
|
656
|
+
|
|
657
|
+
```ts
|
|
658
|
+
animationStep: { Up: 0.2, Down: 0.1 } // different step size per direction
|
|
659
|
+
```
|
|
660
|
+
|
|
487
661
|
```ts
|
|
488
662
|
Motion('swipe-gallery', '.gallery', {
|
|
489
663
|
from: { x: 0 },
|
|
@@ -518,6 +692,18 @@ interface CursorConfig {
|
|
|
518
692
|
}
|
|
519
693
|
```
|
|
520
694
|
|
|
695
|
+
**`CursorStateVars`** is the shape used for `default`, `hover`, and `click`. The `targets` field controls which elements trigger that state:
|
|
696
|
+
|
|
697
|
+
```ts
|
|
698
|
+
interface CursorStateVars {
|
|
699
|
+
targets?: string[]; // CSS selectors that trigger this state (e.g. ['a', 'button', '.btn'])
|
|
700
|
+
duration?: number; // Transition duration in seconds (default: 0.15)
|
|
701
|
+
ease?: string; // Easing (default: 'power3.inOut')
|
|
702
|
+
enabled?: boolean; // Whether state is active (default: true)
|
|
703
|
+
[key: string]: any; // Any CSS property: width, height, backgroundColor, scale, etc.
|
|
704
|
+
}
|
|
705
|
+
```
|
|
706
|
+
|
|
521
707
|
```ts
|
|
522
708
|
Motion('custom-cursor', 'body', {
|
|
523
709
|
to: { opacity: 1 },
|
|
@@ -527,11 +713,52 @@ Motion('custom-cursor', 'body', {
|
|
|
527
713
|
smooth: 0.08,
|
|
528
714
|
hideNative: true,
|
|
529
715
|
default: { width: 12, height: 12, borderRadius: '50%', backgroundColor: '#fff' },
|
|
530
|
-
hover:
|
|
531
|
-
|
|
716
|
+
hover: {
|
|
717
|
+
targets: ['a', 'button', '[data-cursor-hover]'],
|
|
718
|
+
width: 40, height: 40,
|
|
719
|
+
backgroundColor: 'transparent',
|
|
720
|
+
borderColor: '#fff',
|
|
721
|
+
},
|
|
722
|
+
click: { scale: 0.8 },
|
|
723
|
+
});
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
**`type: 'text'`** — reads text from `mp-cursor-text` or `mp-cursor-tooltip` HTML attributes and displays it inside the cursor element. The `text` config object sets CSS properties on the text node:
|
|
727
|
+
|
|
728
|
+
```ts
|
|
729
|
+
Motion('cursor', 'body', { to: { opacity: 1 }, duration: 0 }).onCursor({
|
|
730
|
+
target: '#cursor',
|
|
731
|
+
type: 'text',
|
|
732
|
+
hideNative: true,
|
|
733
|
+
default: { width: 12, height: 12, borderRadius: '50%', backgroundColor: '#fff' },
|
|
734
|
+
hover: { width: 64, height: 64 },
|
|
735
|
+
text: { fontSize: '12px', color: '#000', fontWeight: 'bold' },
|
|
532
736
|
});
|
|
533
737
|
```
|
|
534
738
|
|
|
739
|
+
```html
|
|
740
|
+
<a href="/about" mp-cursor-text="About Us">About</a>
|
|
741
|
+
<button mp-cursor-tooltip="Click me">Button</button>
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
**`type: 'media'`** — reads an image or video URL from the `mp-cursor-media` attribute and renders it inside the cursor. Supports http/https and relative URLs. The `media` config object sets CSS on the media element:
|
|
745
|
+
|
|
746
|
+
```ts
|
|
747
|
+
Motion('cursor', 'body', { to: { opacity: 1 }, duration: 0 }).onCursor({
|
|
748
|
+
target: '#cursor',
|
|
749
|
+
type: 'media',
|
|
750
|
+
hideNative: true,
|
|
751
|
+
default: { width: 48, height: 48, borderRadius: '50%' },
|
|
752
|
+
hover: { width: 120, height: 80 },
|
|
753
|
+
media: { borderRadius: '8px', objectFit: 'cover' },
|
|
754
|
+
});
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
```html
|
|
758
|
+
<div class="project-card" mp-cursor-media="/images/preview.jpg">…</div>
|
|
759
|
+
<div class="video-card" mp-cursor-media="https://cdn.example.com/preview.mp4">…</div>
|
|
760
|
+
```
|
|
761
|
+
|
|
535
762
|
---
|
|
536
763
|
|
|
537
764
|
### Lifecycle Callbacks
|
|
@@ -585,8 +812,10 @@ interface AnimationConfig {
|
|
|
585
812
|
delay?: number; // Seconds before animation begins
|
|
586
813
|
ease?: string; // Easing name string, see Easing section
|
|
587
814
|
stagger?: number | StaggerVars; // Per-element stagger delay
|
|
588
|
-
repeat?: RepeatConfig;
|
|
815
|
+
repeat?: number | RepeatConfig; // Repeat count shorthand or full config (see below)
|
|
589
816
|
split?: SplitType; // Text splitting for per-char/word/line animation
|
|
817
|
+
mask?: boolean; // Wrap split elements in overflow:hidden for reveal effects
|
|
818
|
+
fit?: FitConfig; // FLIP-style morph toward another element
|
|
590
819
|
axis?: 'x' | 'y'; // Axis binding for onMouseMove animations
|
|
591
820
|
|
|
592
821
|
// Lifecycle
|
|
@@ -638,13 +867,68 @@ drawSVG // string | { start?: number; end?: n
|
|
|
638
867
|
|
|
639
868
|
// Motion path
|
|
640
869
|
path: {
|
|
641
|
-
target: string | Element;
|
|
870
|
+
target: string | Element | string; // SVG <path> selector, element, or raw path data (starts with M/m)
|
|
642
871
|
align?: string | Element; // Align bounding box to this element
|
|
643
872
|
alignAt?: [number, number]; // Origin point [x%, y%], default [50, 50]
|
|
644
873
|
start?: number; // Path start (0–1), default 0
|
|
645
874
|
end?: number; // Path end (0–1), default 1
|
|
646
875
|
rotate?: boolean; // Auto-rotate along tangent
|
|
647
876
|
}
|
|
877
|
+
|
|
878
|
+
// CSS custom properties
|
|
879
|
+
'--my-var' // any CSS custom property name (string)
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
### drawSVG
|
|
883
|
+
|
|
884
|
+
Animate the visible portion of an SVG stroke. The element must have a `stroke` and a set `stroke-dasharray` (or the SDK will compute it automatically).
|
|
885
|
+
|
|
886
|
+
| Format | Meaning |
|
|
887
|
+
|--------|---------|
|
|
888
|
+
| `"0% 100%"` | Full stroke visible |
|
|
889
|
+
| `"0% 0%"` | Stroke fully hidden (start position for a draw-in) |
|
|
890
|
+
| `"20% 80%"` | Middle portion only |
|
|
891
|
+
| `"50%"` | Shorthand for `"0% 50%"` |
|
|
892
|
+
| `"100px 500px"` | Pixel range along the stroke |
|
|
893
|
+
| `{ start: 20, end: 80 }` | Object form — values are **percentages 0–100**, not 0–1 |
|
|
894
|
+
|
|
895
|
+
```ts
|
|
896
|
+
// Animate stroke from hidden to fully drawn
|
|
897
|
+
Motion('draw-path', 'path#line', {
|
|
898
|
+
from: { drawSVG: '0% 0%' },
|
|
899
|
+
to: { drawSVG: '0% 100%' },
|
|
900
|
+
duration: 1.2,
|
|
901
|
+
ease: 'power2.inOut',
|
|
902
|
+
}).onPageLoad();
|
|
903
|
+
|
|
904
|
+
// Object format — percentages, not 0–1
|
|
905
|
+
Motion('draw-partial', '#circle', {
|
|
906
|
+
to: { drawSVG: { start: 20, end: 80 } },
|
|
907
|
+
duration: 0.8,
|
|
908
|
+
}).play();
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
### path
|
|
912
|
+
|
|
913
|
+
`path.target` accepts a CSS selector, an `Element`, or raw SVG path data (a string starting with `M` or `m`):
|
|
914
|
+
|
|
915
|
+
```ts
|
|
916
|
+
// Inline path data — no DOM element required
|
|
917
|
+
Motion('fly', '.icon', {
|
|
918
|
+
to: { path: { target: 'M 0 100 C 50 0 150 200 200 100', rotate: true } },
|
|
919
|
+
duration: 2,
|
|
920
|
+
}).play();
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
### CSS Custom Properties
|
|
924
|
+
|
|
925
|
+
CSS custom properties can be animated by passing the property name as a string key:
|
|
926
|
+
|
|
927
|
+
```ts
|
|
928
|
+
Motion('theme', ':root', {
|
|
929
|
+
to: { '--primary-hue': 240, '--accent-opacity': 0.8 },
|
|
930
|
+
duration: 0.5,
|
|
931
|
+
}).onClick({ target: '#theme-btn' });
|
|
648
932
|
```
|
|
649
933
|
|
|
650
934
|
### StaggerVars
|
|
@@ -662,12 +946,23 @@ interface StaggerVars {
|
|
|
662
946
|
|
|
663
947
|
### RepeatConfig
|
|
664
948
|
|
|
949
|
+
The `repeat` field accepts either a plain number or a `RepeatConfig` object:
|
|
950
|
+
|
|
665
951
|
```ts
|
|
952
|
+
// Shorthand — number of additional repetitions
|
|
953
|
+
repeat: 3 // repeat 3 more times after the first play
|
|
954
|
+
repeat: -1 // repeat infinitely
|
|
955
|
+
|
|
956
|
+
// Full config
|
|
666
957
|
interface RepeatConfig {
|
|
667
958
|
times: number; // Number of additional repetitions (-1 = infinite)
|
|
668
959
|
delay?: number; // Seconds between repetitions
|
|
669
960
|
yoyo?: boolean; // Alternate direction each cycle
|
|
670
961
|
}
|
|
962
|
+
|
|
963
|
+
// Examples
|
|
964
|
+
repeat: { times: -1, yoyo: true, delay: 0.2 } // infinite yoyo with pause between cycles
|
|
965
|
+
repeat: { times: 2, yoyo: true, delay: 0.5 } // 2 extra cycles, yoyo, 0.5s pause
|
|
671
966
|
```
|
|
672
967
|
|
|
673
968
|
### SplitType
|
|
@@ -682,7 +977,16 @@ type SplitType =
|
|
|
682
977
|
| 'chars,words,lines';
|
|
683
978
|
```
|
|
684
979
|
|
|
685
|
-
Text is split into wrapper `<span>` elements before animating. `Motion.reset()` reverts the DOM.
|
|
980
|
+
Text is split into wrapper `<span>` elements before animating. `Motion.reset()` reverts the DOM. Inline elements (like `<span class="accent">`) are preserved during splitting.
|
|
981
|
+
|
|
982
|
+
Split elements receive data attributes for CSS targeting:
|
|
983
|
+
|
|
984
|
+
| Attribute | Set on | Index attribute |
|
|
985
|
+
|-----------|--------|-----------------|
|
|
986
|
+
| `[data-split-char]` | each character span | `data-char-index` |
|
|
987
|
+
| `[data-split-word]` | each word span | `data-word-index` |
|
|
988
|
+
| `[data-split-line]` | each line span | `data-line-index` |
|
|
989
|
+
| `[data-split-mask]` | overflow wrapper (when `mask: true`) | — |
|
|
686
990
|
|
|
687
991
|
```ts
|
|
688
992
|
Motion('text-reveal', '.headline', {
|
|
@@ -694,6 +998,48 @@ Motion('text-reveal', '.headline', {
|
|
|
694
998
|
}).onPageLoad();
|
|
695
999
|
```
|
|
696
1000
|
|
|
1001
|
+
### mask
|
|
1002
|
+
|
|
1003
|
+
When `mask: true` is used together with `split`, each split element (char, word, or line) is wrapped in a parent with `overflow: hidden`. This clips animated content to its natural bounds, creating a 'reveal' effect — for example, animating `y: '100%'` makes text slide up from behind an invisible edge.
|
|
1004
|
+
|
|
1005
|
+
```ts
|
|
1006
|
+
Motion('reveal', 'h1', {
|
|
1007
|
+
split: 'lines',
|
|
1008
|
+
mask: true,
|
|
1009
|
+
from: { y: '100%' },
|
|
1010
|
+
to: { y: 0 },
|
|
1011
|
+
stagger: 0.1,
|
|
1012
|
+
}).onPageLoad();
|
|
1013
|
+
```
|
|
1014
|
+
|
|
1015
|
+
### FitConfig
|
|
1016
|
+
|
|
1017
|
+
FLIP-style morph animation. When `fit` is set, `from` and `to` are ignored — the SDK measures both elements' bounding rects at play time and animates the visual delta between them.
|
|
1018
|
+
|
|
1019
|
+
```ts
|
|
1020
|
+
interface FitConfig {
|
|
1021
|
+
/** CSS selector for the target element to morph toward */
|
|
1022
|
+
target: string;
|
|
1023
|
+
/** Convert to absolute positioning during animation. Default: false */
|
|
1024
|
+
absolute?: boolean;
|
|
1025
|
+
/** Include scale changes. Default: true */
|
|
1026
|
+
scale?: boolean;
|
|
1027
|
+
/** Animate actual width/height instead of scaleX/scaleY. Default: false */
|
|
1028
|
+
resize?: boolean;
|
|
1029
|
+
}
|
|
1030
|
+
```
|
|
1031
|
+
|
|
1032
|
+
- **`scale: true`** (default) — animates using `scaleX`/`scaleY`. Fast, GPU-accelerated, but can distort text, borders, and box-shadows.
|
|
1033
|
+
- **`resize: true`** — animates actual `width`/`height` properties instead. No visual distortion, but triggers layout reflow. Mutually exclusive with `scale`.
|
|
1034
|
+
|
|
1035
|
+
```ts
|
|
1036
|
+
Motion('reorder', '.container', {
|
|
1037
|
+
fit: { target: '.item', resize: true },
|
|
1038
|
+
duration: 0.5,
|
|
1039
|
+
ease: 'power2.inOut',
|
|
1040
|
+
}).play();
|
|
1041
|
+
```
|
|
1042
|
+
|
|
697
1043
|
---
|
|
698
1044
|
|
|
699
1045
|
## Easing
|
|
@@ -731,6 +1077,8 @@ Motion('spring-in', '.box', {
|
|
|
731
1077
|
|
|
732
1078
|
All types are re-exported from the package entry point.
|
|
733
1079
|
|
|
1080
|
+
Most users won't need to import types directly — TypeScript infers everything from the `Motion()` function signature, so you get full autocomplete and type checking out of the box. These exports are provided for advanced use cases like building wrapper libraries or typing standalone config objects.
|
|
1081
|
+
|
|
734
1082
|
```ts
|
|
735
1083
|
import type {
|
|
736
1084
|
// Core
|
|
@@ -747,6 +1095,7 @@ import type {
|
|
|
747
1095
|
RepeatConfig,
|
|
748
1096
|
SplitType,
|
|
749
1097
|
PathConfig,
|
|
1098
|
+
FitConfig,
|
|
750
1099
|
|
|
751
1100
|
// Triggers
|
|
752
1101
|
HoverConfig,
|
|
@@ -754,30 +1103,33 @@ import type {
|
|
|
754
1103
|
ScrollConfig,
|
|
755
1104
|
MouseMoveConfig,
|
|
756
1105
|
MarkerConfig,
|
|
1106
|
+
PageExitConfig,
|
|
757
1107
|
|
|
758
1108
|
// Gesture
|
|
759
1109
|
GestureConfig,
|
|
760
1110
|
GestureEvent,
|
|
761
1111
|
GestureAction,
|
|
762
1112
|
GestureInputType,
|
|
1113
|
+
TimelineAction,
|
|
763
1114
|
|
|
764
1115
|
// Cursor
|
|
765
1116
|
CursorConfig,
|
|
766
1117
|
CursorStateVars,
|
|
767
1118
|
CursorSqueezeConfig,
|
|
768
|
-
} from '@motion/sdk';
|
|
1119
|
+
} from '@motion.page/sdk';
|
|
769
1120
|
|
|
770
1121
|
// Namespace import
|
|
771
|
-
import { Types } from '@motion/sdk';
|
|
1122
|
+
import { Types } from '@motion.page/sdk';
|
|
772
1123
|
```
|
|
773
1124
|
|
|
774
1125
|
---
|
|
775
1126
|
|
|
776
1127
|
## Browser Build
|
|
777
1128
|
|
|
778
|
-
A pre-built IIFE bundle
|
|
1129
|
+
A pre-built IIFE bundle (`motion-sdk.browser.js`) is included in the package's `dist/` folder and can be used directly via a `<script>` tag — no bundler required. You can also load it from a CDN that mirrors npm packages (e.g. jsDelivr or unpkg).
|
|
779
1130
|
|
|
780
1131
|
```html
|
|
1132
|
+
<!-- From your own server / CDN -->
|
|
781
1133
|
<script src="motion-sdk.browser.js"></script>
|
|
782
1134
|
<script>
|
|
783
1135
|
// Globals are exposed on window:
|
|
@@ -791,12 +1143,6 @@ A pre-built IIFE bundle is generated at `dist/motion-sdk.browser.js` for direct
|
|
|
791
1143
|
</script>
|
|
792
1144
|
```
|
|
793
1145
|
|
|
794
|
-
To regenerate the browser bundle:
|
|
795
|
-
|
|
796
|
-
```bash
|
|
797
|
-
bun run packages/sdk/scripts/build-iife.ts
|
|
798
|
-
```
|
|
799
|
-
|
|
800
1146
|
---
|
|
801
1147
|
|
|
802
1148
|
## Browser Support
|