@motion.page/sdk 0.1.0 → 0.1.2
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/LICENSE +113 -0
- package/README.md +487 -59
- 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/llms.txt +64 -0
- package/package.json +4 -8
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
|
+
[](https://bundlephobia.com/package/@motion.page/sdk)
|
|
7
|
+
[](./LICENSE)
|
|
6
8
|
|
|
7
9
|
---
|
|
8
10
|
|
|
@@ -11,6 +13,7 @@ A high-performance CSS animation SDK with a declarative, config-based API. Zero
|
|
|
11
13
|
- [Installation](#installation)
|
|
12
14
|
- [Quick Start](#quick-start)
|
|
13
15
|
- [Core Concept](#core-concept)
|
|
16
|
+
- [Implicit Values](#implicit-values)
|
|
14
17
|
- [API Reference](#api-reference)
|
|
15
18
|
- [Motion()](#motion-function)
|
|
16
19
|
- [Motion Static Methods](#motion-static-methods)
|
|
@@ -30,11 +33,20 @@ A high-performance CSS animation SDK with a declarative, config-based API. Zero
|
|
|
30
33
|
## Installation
|
|
31
34
|
|
|
32
35
|
```bash
|
|
33
|
-
|
|
34
|
-
#
|
|
36
|
+
npm install @motion.page/sdk
|
|
37
|
+
# or
|
|
38
|
+
bun add @motion.page/sdk
|
|
39
|
+
# or
|
|
40
|
+
yarn add @motion.page/sdk
|
|
41
|
+
# or
|
|
42
|
+
pnpm add @motion.page/sdk
|
|
35
43
|
```
|
|
36
44
|
|
|
37
|
-
|
|
45
|
+
📖 Full documentation and interactive examples: [motion.page](https://motion.page)
|
|
46
|
+
|
|
47
|
+
> **⚠️ Browser-only:** This SDK requires a browser environment (`document`, `window`). In SSR frameworks (Next.js, Nuxt, Astro), wrap SDK calls in `useEffect`, `onMounted`, or client-side scripts.
|
|
48
|
+
|
|
49
|
+
For direct browser use without a bundler, see [Browser Build](#browser-build).
|
|
38
50
|
|
|
39
51
|
---
|
|
40
52
|
|
|
@@ -43,12 +55,11 @@ For direct browser use, see [Browser Build](#browser-build).
|
|
|
43
55
|
### Basic Animation
|
|
44
56
|
|
|
45
57
|
```ts
|
|
46
|
-
import { Motion } from '@motion/sdk';
|
|
58
|
+
import { Motion } from '@motion.page/sdk';
|
|
47
59
|
|
|
48
60
|
// Fade in and slide up
|
|
49
61
|
Motion('hero-intro', '#hero', {
|
|
50
62
|
from: { opacity: 0, y: 50 },
|
|
51
|
-
to: { opacity: 1, y: 0 },
|
|
52
63
|
duration: 0.8,
|
|
53
64
|
ease: 'power2.out',
|
|
54
65
|
}).play();
|
|
@@ -60,7 +71,6 @@ Motion('hero-intro', '#hero', {
|
|
|
60
71
|
// Scrub animation progress to scroll position
|
|
61
72
|
Motion('scroll-reveal', '.card', {
|
|
62
73
|
from: { opacity: 0, y: 40 },
|
|
63
|
-
to: { opacity: 1, y: 0 },
|
|
64
74
|
duration: 0.6,
|
|
65
75
|
}).onScroll({ scrub: true, start: 'top 80%', end: 'top 30%' });
|
|
66
76
|
```
|
|
@@ -84,13 +94,11 @@ Motion('intro-sequence', [
|
|
|
84
94
|
{
|
|
85
95
|
target: '.title',
|
|
86
96
|
from: { opacity: 0, y: -30 },
|
|
87
|
-
to: { opacity: 1, y: 0 },
|
|
88
97
|
duration: 0.6,
|
|
89
98
|
},
|
|
90
99
|
{
|
|
91
100
|
target: '.cards',
|
|
92
101
|
from: { opacity: 0, y: 20 },
|
|
93
|
-
to: { opacity: 1, y: 0 },
|
|
94
102
|
duration: 0.5,
|
|
95
103
|
stagger: { each: 0.1, from: 'start' },
|
|
96
104
|
position: '+=0.1', // starts 0.1s after the previous entry ends
|
|
@@ -98,7 +106,6 @@ Motion('intro-sequence', [
|
|
|
98
106
|
{
|
|
99
107
|
target: '.cta',
|
|
100
108
|
from: { opacity: 0, scale: 0.9 },
|
|
101
|
-
to: { opacity: 1, scale: 1 },
|
|
102
109
|
duration: 0.4,
|
|
103
110
|
position: '<', // starts at the same time as the previous entry
|
|
104
111
|
},
|
|
@@ -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,90 @@ 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 }, duration: 0.8 }).onPageLoad();
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Implicit Values
|
|
156
|
+
|
|
157
|
+
The SDK automatically resolves a missing `from` or `to` by reading the element's **current computed CSS** at build time (i.e. when `Motion()` is first called). This means you rarely need to specify both ends of an animation.
|
|
158
|
+
|
|
159
|
+
### Three cases
|
|
160
|
+
|
|
161
|
+
| Config | SDK behaviour |
|
|
162
|
+
|--------|--------------|
|
|
163
|
+
| `from` only | Reads current CSS as the `to` target. Animate **from** custom values **into** the element's natural state. |
|
|
164
|
+
| `to` only | Reads current CSS as the `from` starting point. Animate **from** the natural state **to** custom values. |
|
|
165
|
+
| Both | Both endpoints are explicit. Only needed when **neither** endpoint matches the element's natural CSS. |
|
|
166
|
+
|
|
167
|
+
### Common pattern — reveal animations only need `from`
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
// ❌ Redundant — opacity:1 and y:0 are the element's natural CSS defaults
|
|
171
|
+
Motion('reveal', '.card', {
|
|
172
|
+
from: { opacity: 0, y: 40 },
|
|
173
|
+
to: { opacity: 1, y: 0 },
|
|
174
|
+
duration: 0.6,
|
|
175
|
+
}).onScroll({ scrub: true });
|
|
176
|
+
|
|
177
|
+
// ✅ Correct — SDK reads opacity:1 and y:0 from computed CSS automatically
|
|
178
|
+
Motion('reveal', '.card', {
|
|
179
|
+
from: { opacity: 0, y: 40 },
|
|
180
|
+
duration: 0.6,
|
|
181
|
+
}).onScroll({ scrub: true });
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### When `to` only is correct
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
// Animate FROM the element's current state TO a hover state
|
|
188
|
+
Motion('btn-hover', '.btn', {
|
|
189
|
+
to: { scale: 1.05, backgroundColor: '#0099ff' },
|
|
190
|
+
duration: 0.3,
|
|
191
|
+
}).onHover({ onLeave: 'reverse' });
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### When you need both
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
// Neither endpoint is the element's natural state
|
|
198
|
+
Motion('parallax', '.layer', {
|
|
199
|
+
from: { x: -20, y: -20 },
|
|
200
|
+
to: { x: 20, y: 20 },
|
|
201
|
+
}).onMouseMove({ type: 'axis' });
|
|
202
|
+
|
|
203
|
+
// Animating between two non-default positions
|
|
204
|
+
Motion('swipe', '.panel', {
|
|
205
|
+
from: { x: -100 },
|
|
206
|
+
to: { x: 100 },
|
|
207
|
+
}).onGesture({ types: ['touch'], events: { Left: 'play', Right: 'reverse' } });
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Natural CSS defaults (common values the SDK resolves automatically)
|
|
211
|
+
|
|
212
|
+
| Property | Natural default |
|
|
213
|
+
|----------|----------------|
|
|
214
|
+
| `opacity` | `1` |
|
|
215
|
+
| `x`, `y`, `z` | `0` |
|
|
216
|
+
| `scale`, `scaleX`, `scaleY` | `1` |
|
|
217
|
+
| `rotate`, `rotateX`, `rotateY` | `0` |
|
|
218
|
+
| `skewX`, `skewY` | `0` |
|
|
219
|
+
|
|
220
|
+
> **Note:** `height: 'auto'` is **not** a natural default for the animation engine — it must be specified explicitly in `to` when needed (e.g. accordion reveals).
|
|
221
|
+
|
|
222
|
+
### Build-time vs. play-time
|
|
223
|
+
|
|
224
|
+
The SDK reads computed CSS **at build time** (when `Motion()` is called), not at play time. If the element's styles change after the timeline is created, call `.kill()` and rebuild the timeline.
|
|
225
|
+
|
|
226
|
+
### Edge case — transform cache
|
|
227
|
+
|
|
228
|
+
Transform properties (`x`, `y`, `scale`, `rotate`, etc.) are read from the SDK's **internal transform cache** rather than `getComputedStyle`. This ensures composited transforms remain consistent across animations. Plain CSS properties (`opacity`, `color`, `width`, etc.) are read directly from `getComputedStyle`.
|
|
229
|
+
|
|
123
230
|
---
|
|
124
231
|
|
|
125
232
|
## API Reference
|
|
@@ -149,7 +256,7 @@ Calling `Motion()` with the same name on an already-existing timeline returns it
|
|
|
149
256
|
|
|
150
257
|
| Method | Signature | Description |
|
|
151
258
|
|--------|-----------|-------------|
|
|
152
|
-
| `Motion.set` | `(target: TargetInput, vars: AnimationVars): void` | Immediately apply CSS / transform properties with no animation (
|
|
259
|
+
| `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
260
|
| `Motion.get` | `(name: string): Timeline \| undefined` | Get a timeline by name; returns `undefined` if none exists. |
|
|
154
261
|
| `Motion.has` | `(name: string): boolean` | Check whether a named timeline is registered. |
|
|
155
262
|
| `Motion.getNames` | `(): string[]` | Return the names of all registered timelines. |
|
|
@@ -184,6 +291,13 @@ Motion.reset('.animated-card');
|
|
|
184
291
|
|
|
185
292
|
GSAP-compatible utility functions accessible via `Motion.utils`. These are drop-in replacements for `gsap.utils.*` helpers.
|
|
186
293
|
|
|
294
|
+
`MotionUtils` is also exported directly from the package and can be imported independently:
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
import { MotionUtils } from '@motion.page/sdk';
|
|
298
|
+
// Same object as Motion.utils
|
|
299
|
+
```
|
|
300
|
+
|
|
187
301
|
| Method | Signature | Description |
|
|
188
302
|
|--------|-----------|-------------|
|
|
189
303
|
| `toArray` | `(target, scope?) → Element[]` | Convert CSS selector, NodeList, HTMLCollection, or Element to a flat array. Drop-in for `gsap.utils.toArray()`. |
|
|
@@ -297,9 +411,11 @@ tl.call(
|
|
|
297
411
|
| `"-=0.3"` | 0.3 s before the previous entry ends |
|
|
298
412
|
| `"<"` | At the same start time as the previous entry |
|
|
299
413
|
| `">"` | Immediately after the previous entry ends |
|
|
414
|
+
| `"<0.2"` | 0.2 s after the start of the previous entry |
|
|
415
|
+
| `">-0.1"` | 0.1 s before the end of the previous entry |
|
|
300
416
|
|
|
301
417
|
```ts
|
|
302
|
-
Motion('demo', '.box', { from: { opacity: 0 },
|
|
418
|
+
Motion('demo', '.box', { from: { opacity: 0 }, duration: 1 })
|
|
303
419
|
.call(() => console.log('halfway'), [], 0.5)
|
|
304
420
|
.call(() => console.log('done'), [], '>');
|
|
305
421
|
```
|
|
@@ -320,6 +436,22 @@ tl.clear(): this
|
|
|
320
436
|
|
|
321
437
|
All trigger methods are chainable and attach behaviour to the timeline without requiring you to manage event listeners manually.
|
|
322
438
|
|
|
439
|
+
#### Per-Element Triggers (`each: true`)
|
|
440
|
+
|
|
441
|
+
When targeting multiple elements, `each: true` creates independent per-element timeline instances. Without it, all matched elements share one timeline and play/reverse together.
|
|
442
|
+
|
|
443
|
+
```ts
|
|
444
|
+
// WITHOUT each — hovering any card plays ALL cards
|
|
445
|
+
Motion('card-hover', '.card', { to: { y: -8 }, duration: 0.3 })
|
|
446
|
+
.onHover({ onLeave: 'reverse' });
|
|
447
|
+
|
|
448
|
+
// WITH each — each card animates independently
|
|
449
|
+
Motion('card-hover', '.card', { to: { y: -8 }, duration: 0.3 })
|
|
450
|
+
.onHover({ each: true, onLeave: 'reverse' });
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
`each` is supported by `.onHover()`, `.onClick()`, `.onScroll()`, `.onMouseMove()`, and `.onGesture()`.
|
|
454
|
+
|
|
323
455
|
#### `.onHover(config?)`
|
|
324
456
|
|
|
325
457
|
Play on `mouseenter`, react on `mouseleave`.
|
|
@@ -358,7 +490,7 @@ interface ClickConfig {
|
|
|
358
490
|
```ts
|
|
359
491
|
Motion('menu-toggle', '#menu', {
|
|
360
492
|
from: { height: 0, opacity: 0 },
|
|
361
|
-
to: { height: 'auto',
|
|
493
|
+
to: { height: 'auto' }, // height: 'auto' must be explicit
|
|
362
494
|
duration: 0.4,
|
|
363
495
|
ease: 'power2.inOut',
|
|
364
496
|
}).onClick({ target: '#menu-btn', toggle: 'reverse' });
|
|
@@ -375,12 +507,55 @@ interface ScrollConfig {
|
|
|
375
507
|
end?: string; // e.g. 'bottom 20%'
|
|
376
508
|
scrub?: boolean | number; // true = instant, number = smoothing seconds
|
|
377
509
|
snap?: number | number[] | ((progress: number) => number); // Snap scroll progress
|
|
378
|
-
markers?: boolean | MarkerConfig; // Debug markers
|
|
510
|
+
markers?: boolean | MarkerConfig; // Debug markers (pass object for styling)
|
|
379
511
|
scroller?: string | Element; // Custom scroll container
|
|
380
|
-
pin?: boolean | string; //
|
|
512
|
+
pin?: boolean | string; // true = pin animation target; string = pin a different element
|
|
381
513
|
pinSpacing?: boolean | 'margin' | 'padding';
|
|
382
514
|
each?: boolean;
|
|
383
|
-
toggleActions?: string; //
|
|
515
|
+
toggleActions?: string; // Format: 'onEnter onLeave onEnterBack onLeaveBack'
|
|
516
|
+
}
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
**`start` / `end` defaults:**
|
|
520
|
+
- Without `pin`: `start: 'top bottom'`, `end: 'bottom top'`
|
|
521
|
+
- With `pin`: `start: 'top top'`, `end: 'bottom top'`
|
|
522
|
+
|
|
523
|
+
**Relative `end` with `+=`:** When `end` starts with `+=`, the value is a distance measured from the `start` position:
|
|
524
|
+
|
|
525
|
+
```ts
|
|
526
|
+
.onScroll({ start: 'top center', end: '+=800' }) // 800px of scroll travel
|
|
527
|
+
.onScroll({ start: 'top top', end: '+=100vh' }) // one viewport height of scroll
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
**`pin: string`** pins a different element (e.g. a parent wrapper) while the animated child scrolls:
|
|
531
|
+
|
|
532
|
+
```ts
|
|
533
|
+
// Pin the parent section while the child content animates
|
|
534
|
+
Motion('content-reveal', '.content', {
|
|
535
|
+
from: { opacity: 0, y: 40 },
|
|
536
|
+
duration: 1,
|
|
537
|
+
}).onScroll({ scrub: true, pin: '.section-wrapper', start: 'top top', end: '+=600' });
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
**`toggleActions`** controls what happens at each scroll boundary. Format: `"onEnter onLeave onEnterBack onLeaveBack"`. Default: `"play reverse play reverse"`. Valid actions: `play`, `pause`, `resume`, `reverse`, `restart`, `reset`, `complete`, `none`.
|
|
541
|
+
|
|
542
|
+
```ts
|
|
543
|
+
// Play once — never reverse (common for reveal animations)
|
|
544
|
+
.onScroll({ toggleActions: 'play none none none' });
|
|
545
|
+
|
|
546
|
+
// Re-animate every time it enters the viewport
|
|
547
|
+
.onScroll({ toggleActions: 'restart none none reset' });
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
**`markers`** accepts `true` for default debug markers, or a `MarkerConfig` object for custom styling:
|
|
551
|
+
|
|
552
|
+
```ts
|
|
553
|
+
interface MarkerConfig {
|
|
554
|
+
startColor?: string; // Default: 'green'
|
|
555
|
+
endColor?: string; // Default: 'red'
|
|
556
|
+
fontSize?: string; // e.g. '12px'
|
|
557
|
+
fontWeight?: string;
|
|
558
|
+
indent?: number; // Horizontal offset in pixels
|
|
384
559
|
}
|
|
385
560
|
```
|
|
386
561
|
|
|
@@ -408,8 +583,7 @@ Motion('h-scroll', '.panel', {
|
|
|
408
583
|
|
|
409
584
|
```ts
|
|
410
585
|
Motion('parallax', '.hero-bg', {
|
|
411
|
-
|
|
412
|
-
to: { y: -100 },
|
|
586
|
+
to: { y: -100 },
|
|
413
587
|
}).onScroll({ scrub: 1, start: 'top top', end: 'bottom top' });
|
|
414
588
|
```
|
|
415
589
|
|
|
@@ -428,6 +602,15 @@ interface MouseMoveConfig {
|
|
|
428
602
|
}
|
|
429
603
|
```
|
|
430
604
|
|
|
605
|
+
**Defaults:**
|
|
606
|
+
|
|
607
|
+
| Option | Default | Notes |
|
|
608
|
+
|--------|---------|-------|
|
|
609
|
+
| `type` | `'distance'` | |
|
|
610
|
+
| `startProgress` | `0.5` | Progress value when mouse is at rest |
|
|
611
|
+
| `leaveProgress` | `0.5` | Progress to animate to on mouse leave |
|
|
612
|
+
| `smooth` | `0.1` | `0` = instant tracking, `1` = maximum lag |
|
|
613
|
+
|
|
431
614
|
```ts
|
|
432
615
|
Motion('parallax-depth', '.layer', {
|
|
433
616
|
from: { x: -20, y: -20 },
|
|
@@ -438,15 +621,54 @@ Motion('parallax-depth', '.layer', {
|
|
|
438
621
|
|
|
439
622
|
#### `.onPageLoad()`
|
|
440
623
|
|
|
441
|
-
Play the animation automatically when the page finishes loading.
|
|
624
|
+
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
625
|
|
|
443
626
|
```ts
|
|
444
627
|
Motion('page-intro', [
|
|
445
|
-
{ target: '.logo', from: { opacity: 0 },
|
|
446
|
-
{ target: '.nav', from: { y: -20, opacity: 0 },
|
|
628
|
+
{ target: '.logo', from: { opacity: 0 }, duration: 0.5 },
|
|
629
|
+
{ target: '.nav', from: { y: -20, opacity: 0 }, duration: 0.4 },
|
|
447
630
|
]).onPageLoad();
|
|
448
631
|
```
|
|
449
632
|
|
|
633
|
+
#### `.onPageExit(config?)`
|
|
634
|
+
|
|
635
|
+
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.
|
|
636
|
+
|
|
637
|
+
```ts
|
|
638
|
+
interface PageExitConfig {
|
|
639
|
+
/** 'all' (default) | 'include' | 'exclude' */
|
|
640
|
+
mode?: 'all' | 'include' | 'exclude';
|
|
641
|
+
/** CSS selectors for links — required when mode is 'include' or 'exclude' */
|
|
642
|
+
selectors?: string;
|
|
643
|
+
/** Href patterns to skip automatically. Note: 'mailto' also skips tel: links. */
|
|
644
|
+
skipHref?: ('anchor' | 'javascript' | 'mailto')[];
|
|
645
|
+
}
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
- `mode: 'all'` (default) — intercepts every `<a>` on the page
|
|
649
|
+
- `mode: 'include'` — only links matching `selectors`
|
|
650
|
+
- `mode: 'exclude'` — all links except those matching `selectors`
|
|
651
|
+
- Automatically skips `target="_blank"` links and modifier-key clicks (Cmd/Ctrl/Shift/Alt)
|
|
652
|
+
|
|
653
|
+
```ts
|
|
654
|
+
// Fade-out on page exit — all links
|
|
655
|
+
Motion('page-exit', 'body', {
|
|
656
|
+
to: { opacity: 0 },
|
|
657
|
+
duration: 0.4,
|
|
658
|
+
ease: 'power2.in',
|
|
659
|
+
}).onPageExit();
|
|
660
|
+
|
|
661
|
+
// Only internal nav links
|
|
662
|
+
Motion('page-exit', 'body', {
|
|
663
|
+
to: { opacity: 0 },
|
|
664
|
+
duration: 0.4,
|
|
665
|
+
}).onPageExit({
|
|
666
|
+
mode: 'include',
|
|
667
|
+
selectors: 'nav a',
|
|
668
|
+
skipHref: ['anchor', 'mailto'],
|
|
669
|
+
});
|
|
670
|
+
```
|
|
671
|
+
|
|
450
672
|
#### `.onGesture(config)`
|
|
451
673
|
|
|
452
674
|
Respond to pointer, touch, wheel, or scroll gestures with fine-grained event-to-action mapping.
|
|
@@ -484,10 +706,36 @@ interface GestureConfig {
|
|
|
484
706
|
}
|
|
485
707
|
```
|
|
486
708
|
|
|
709
|
+
**Config defaults:**
|
|
710
|
+
|
|
711
|
+
| Option | Default | Unit | Notes |
|
|
712
|
+
|--------|---------|------|-------|
|
|
713
|
+
| `tolerance` | `1` | px | Min movement before direction events fire |
|
|
714
|
+
| `dragMinimum` | `10` | px | Distance before `Drag` fires |
|
|
715
|
+
| `wheelSpeed` | `1` | multiplier | Scales wheel delta |
|
|
716
|
+
| `scrollSpeed` | `1` | multiplier | Scales scroll delta |
|
|
717
|
+
| `stopDelay` | `150` | ms | Idle time after movement before `Stop` fires |
|
|
718
|
+
| `smooth` | `0` | 0–1 | Smoothness for `progressUp`/`progressDown` actions |
|
|
719
|
+
| `animationStep` | `0.1` | 0–1 | Progress step per event for `progressUp`/`progressDown` |
|
|
720
|
+
|
|
721
|
+
**Event distinctions:**
|
|
722
|
+
|
|
723
|
+
- `Up`/`Down`/`Left`/`Right` — fire **continuously** during movement
|
|
724
|
+
- `UpComplete`/`DownComplete`/etc. — fire **once on release** if that direction was active
|
|
725
|
+
- `PressInit` — fires immediately on press, before start position is recorded
|
|
726
|
+
- `Press` — fires after start position is recorded
|
|
727
|
+
- `Hover`/`HoverEnd` — require a `target` element (not the window)
|
|
728
|
+
- `playNext`/`playPrevious` actions — only work when `each: true` is set
|
|
729
|
+
|
|
730
|
+
`animationStep` can be a single number or a per-event map:
|
|
731
|
+
|
|
732
|
+
```ts
|
|
733
|
+
animationStep: { Up: 0.2, Down: 0.1 } // different step size per direction
|
|
734
|
+
```
|
|
735
|
+
|
|
487
736
|
```ts
|
|
488
737
|
Motion('swipe-gallery', '.gallery', {
|
|
489
|
-
|
|
490
|
-
to: { x: -100 },
|
|
738
|
+
to: { x: -100 },
|
|
491
739
|
}).onGesture({
|
|
492
740
|
types: ['pointer', 'touch'],
|
|
493
741
|
events: {
|
|
@@ -518,6 +766,18 @@ interface CursorConfig {
|
|
|
518
766
|
}
|
|
519
767
|
```
|
|
520
768
|
|
|
769
|
+
**`CursorStateVars`** is the shape used for `default`, `hover`, and `click`. The `targets` field controls which elements trigger that state:
|
|
770
|
+
|
|
771
|
+
```ts
|
|
772
|
+
interface CursorStateVars {
|
|
773
|
+
targets?: string[]; // CSS selectors that trigger this state (e.g. ['a', 'button', '.btn'])
|
|
774
|
+
duration?: number; // Transition duration in seconds (default: 0.15)
|
|
775
|
+
ease?: string; // Easing (default: 'power3.inOut')
|
|
776
|
+
enabled?: boolean; // Whether state is active (default: true)
|
|
777
|
+
[key: string]: any; // Any CSS property: width, height, backgroundColor, scale, etc.
|
|
778
|
+
}
|
|
779
|
+
```
|
|
780
|
+
|
|
521
781
|
```ts
|
|
522
782
|
Motion('custom-cursor', 'body', {
|
|
523
783
|
to: { opacity: 1 },
|
|
@@ -527,11 +787,52 @@ Motion('custom-cursor', 'body', {
|
|
|
527
787
|
smooth: 0.08,
|
|
528
788
|
hideNative: true,
|
|
529
789
|
default: { width: 12, height: 12, borderRadius: '50%', backgroundColor: '#fff' },
|
|
530
|
-
hover:
|
|
531
|
-
|
|
790
|
+
hover: {
|
|
791
|
+
targets: ['a', 'button', '[data-cursor-hover]'],
|
|
792
|
+
width: 40, height: 40,
|
|
793
|
+
backgroundColor: 'transparent',
|
|
794
|
+
borderColor: '#fff',
|
|
795
|
+
},
|
|
796
|
+
click: { scale: 0.8 },
|
|
797
|
+
});
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
**`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:
|
|
801
|
+
|
|
802
|
+
```ts
|
|
803
|
+
Motion('cursor', 'body', { to: { opacity: 1 }, duration: 0 }).onCursor({
|
|
804
|
+
target: '#cursor',
|
|
805
|
+
type: 'text',
|
|
806
|
+
hideNative: true,
|
|
807
|
+
default: { width: 12, height: 12, borderRadius: '50%', backgroundColor: '#fff' },
|
|
808
|
+
hover: { width: 64, height: 64 },
|
|
809
|
+
text: { fontSize: '12px', color: '#000', fontWeight: 'bold' },
|
|
810
|
+
});
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
```html
|
|
814
|
+
<a href="/about" mp-cursor-text="About Us">About</a>
|
|
815
|
+
<button mp-cursor-tooltip="Click me">Button</button>
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
**`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:
|
|
819
|
+
|
|
820
|
+
```ts
|
|
821
|
+
Motion('cursor', 'body', { to: { opacity: 1 }, duration: 0 }).onCursor({
|
|
822
|
+
target: '#cursor',
|
|
823
|
+
type: 'media',
|
|
824
|
+
hideNative: true,
|
|
825
|
+
default: { width: 48, height: 48, borderRadius: '50%' },
|
|
826
|
+
hover: { width: 120, height: 80 },
|
|
827
|
+
media: { borderRadius: '8px', objectFit: 'cover' },
|
|
532
828
|
});
|
|
533
829
|
```
|
|
534
830
|
|
|
831
|
+
```html
|
|
832
|
+
<div class="project-card" mp-cursor-media="/images/preview.jpg">…</div>
|
|
833
|
+
<div class="video-card" mp-cursor-media="https://cdn.example.com/preview.mp4">…</div>
|
|
834
|
+
```
|
|
835
|
+
|
|
535
836
|
---
|
|
536
837
|
|
|
537
838
|
### Lifecycle Callbacks
|
|
@@ -543,7 +844,6 @@ Attach callbacks via the `AnimationConfig` object or directly on the `Timeline`
|
|
|
543
844
|
```ts
|
|
544
845
|
Motion('slide-in', '.card', {
|
|
545
846
|
from: { opacity: 0, x: -40 },
|
|
546
|
-
to: { opacity: 1, x: 0 },
|
|
547
847
|
duration: 0.6,
|
|
548
848
|
onStart: () => console.log('started'),
|
|
549
849
|
onUpdate: (progress) => console.log('progress:', progress),
|
|
@@ -557,7 +857,7 @@ Motion('slide-in', '.card', {
|
|
|
557
857
|
#### Via `Timeline` Methods
|
|
558
858
|
|
|
559
859
|
```ts
|
|
560
|
-
Motion('slide-in', '.card', { from: { opacity: 0 },
|
|
860
|
+
Motion('slide-in', '.card', { from: { opacity: 0 }, duration: 0.6 })
|
|
561
861
|
.onStart(() => console.log('started'))
|
|
562
862
|
.onUpdate((progress, time) => console.log(progress, time))
|
|
563
863
|
.onComplete(() => console.log('done'));
|
|
@@ -585,8 +885,10 @@ interface AnimationConfig {
|
|
|
585
885
|
delay?: number; // Seconds before animation begins
|
|
586
886
|
ease?: string; // Easing name string, see Easing section
|
|
587
887
|
stagger?: number | StaggerVars; // Per-element stagger delay
|
|
588
|
-
repeat?: RepeatConfig;
|
|
888
|
+
repeat?: number | RepeatConfig; // Repeat count shorthand or full config (see below)
|
|
589
889
|
split?: SplitType; // Text splitting for per-char/word/line animation
|
|
890
|
+
mask?: boolean; // Wrap split elements in overflow:hidden for reveal effects
|
|
891
|
+
fit?: FitConfig; // FLIP-style morph toward another element
|
|
590
892
|
axis?: 'x' | 'y'; // Axis binding for onMouseMove animations
|
|
591
893
|
|
|
592
894
|
// Lifecycle
|
|
@@ -638,13 +940,68 @@ drawSVG // string | { start?: number; end?: n
|
|
|
638
940
|
|
|
639
941
|
// Motion path
|
|
640
942
|
path: {
|
|
641
|
-
target: string | Element; // SVG <path> selector or
|
|
943
|
+
target: string | Element; // SVG <path> selector, element, or raw path data (starts with M/m)
|
|
642
944
|
align?: string | Element; // Align bounding box to this element
|
|
643
945
|
alignAt?: [number, number]; // Origin point [x%, y%], default [50, 50]
|
|
644
946
|
start?: number; // Path start (0–1), default 0
|
|
645
947
|
end?: number; // Path end (0–1), default 1
|
|
646
948
|
rotate?: boolean; // Auto-rotate along tangent
|
|
647
949
|
}
|
|
950
|
+
|
|
951
|
+
// CSS custom properties
|
|
952
|
+
'--my-var' // any CSS custom property name (string)
|
|
953
|
+
```
|
|
954
|
+
|
|
955
|
+
### drawSVG
|
|
956
|
+
|
|
957
|
+
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).
|
|
958
|
+
|
|
959
|
+
| Format | Meaning |
|
|
960
|
+
|--------|---------|
|
|
961
|
+
| `"0% 100%"` | Full stroke visible |
|
|
962
|
+
| `"0% 0%"` | Stroke fully hidden (start position for a draw-in) |
|
|
963
|
+
| `"20% 80%"` | Middle portion only |
|
|
964
|
+
| `"50%"` | Shorthand for `"0% 50%"` |
|
|
965
|
+
| `"100px 500px"` | Pixel range along the stroke |
|
|
966
|
+
| `{ start: 20, end: 80 }` | Object form — values are **percentages 0–100**, not 0–1 |
|
|
967
|
+
|
|
968
|
+
```ts
|
|
969
|
+
// Animate stroke from hidden to fully drawn
|
|
970
|
+
Motion('draw-path', 'path#line', {
|
|
971
|
+
from: { drawSVG: '0% 0%' },
|
|
972
|
+
to: { drawSVG: '0% 100%' },
|
|
973
|
+
duration: 1.2,
|
|
974
|
+
ease: 'power2.inOut',
|
|
975
|
+
}).onPageLoad();
|
|
976
|
+
|
|
977
|
+
// Object format — percentages, not 0–1
|
|
978
|
+
Motion('draw-partial', '#circle', {
|
|
979
|
+
to: { drawSVG: { start: 20, end: 80 } },
|
|
980
|
+
duration: 0.8,
|
|
981
|
+
}).play();
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
### path
|
|
985
|
+
|
|
986
|
+
`path.target` accepts a CSS selector, an `Element`, or raw SVG path data (a string starting with `M` or `m`):
|
|
987
|
+
|
|
988
|
+
```ts
|
|
989
|
+
// Inline path data — no DOM element required
|
|
990
|
+
Motion('fly', '.icon', {
|
|
991
|
+
to: { path: { target: 'M 0 100 C 50 0 150 200 200 100', rotate: true } },
|
|
992
|
+
duration: 2,
|
|
993
|
+
}).play();
|
|
994
|
+
```
|
|
995
|
+
|
|
996
|
+
### CSS Custom Properties
|
|
997
|
+
|
|
998
|
+
CSS custom properties can be animated by passing the property name as a string key:
|
|
999
|
+
|
|
1000
|
+
```ts
|
|
1001
|
+
Motion('theme', ':root', {
|
|
1002
|
+
to: { '--primary-hue': 240, '--accent-opacity': 0.8 },
|
|
1003
|
+
duration: 0.5,
|
|
1004
|
+
}).onClick({ target: '#theme-btn' });
|
|
648
1005
|
```
|
|
649
1006
|
|
|
650
1007
|
### StaggerVars
|
|
@@ -662,12 +1019,23 @@ interface StaggerVars {
|
|
|
662
1019
|
|
|
663
1020
|
### RepeatConfig
|
|
664
1021
|
|
|
1022
|
+
The `repeat` field accepts either a plain number or a `RepeatConfig` object:
|
|
1023
|
+
|
|
665
1024
|
```ts
|
|
1025
|
+
// Shorthand — number of additional repetitions
|
|
1026
|
+
repeat: 3 // repeat 3 more times after the first play
|
|
1027
|
+
repeat: -1 // repeat infinitely
|
|
1028
|
+
|
|
1029
|
+
// Full config
|
|
666
1030
|
interface RepeatConfig {
|
|
667
1031
|
times: number; // Number of additional repetitions (-1 = infinite)
|
|
668
1032
|
delay?: number; // Seconds between repetitions
|
|
669
1033
|
yoyo?: boolean; // Alternate direction each cycle
|
|
670
1034
|
}
|
|
1035
|
+
|
|
1036
|
+
// Examples
|
|
1037
|
+
repeat: { times: -1, yoyo: true, delay: 0.2 } // infinite yoyo with pause between cycles
|
|
1038
|
+
repeat: { times: 2, yoyo: true, delay: 0.5 } // 2 extra cycles, yoyo, 0.5s pause
|
|
671
1039
|
```
|
|
672
1040
|
|
|
673
1041
|
### SplitType
|
|
@@ -682,18 +1050,67 @@ type SplitType =
|
|
|
682
1050
|
| 'chars,words,lines';
|
|
683
1051
|
```
|
|
684
1052
|
|
|
685
|
-
Text is split into wrapper `<span>` elements before animating. `Motion.reset()` reverts the DOM.
|
|
1053
|
+
Text is split into wrapper `<span>` elements before animating. `Motion.reset()` reverts the DOM. Inline elements (like `<span class="accent">`) are preserved during splitting.
|
|
1054
|
+
|
|
1055
|
+
Split elements receive data attributes for CSS targeting:
|
|
1056
|
+
|
|
1057
|
+
| Attribute | Set on | Index attribute |
|
|
1058
|
+
|-----------|--------|-----------------|
|
|
1059
|
+
| `[data-split-char]` | each character span | `data-char-index` |
|
|
1060
|
+
| `[data-split-word]` | each word span | `data-word-index` |
|
|
1061
|
+
| `[data-split-line]` | each line span | `data-line-index` |
|
|
1062
|
+
| `[data-split-mask]` | overflow wrapper (when `mask: true`) | — |
|
|
686
1063
|
|
|
687
1064
|
```ts
|
|
688
1065
|
Motion('text-reveal', '.headline', {
|
|
689
1066
|
from: { opacity: 0, y: 20 },
|
|
690
|
-
to: { opacity: 1, y: 0 },
|
|
691
1067
|
duration: 0.5,
|
|
692
1068
|
split: 'chars',
|
|
693
1069
|
stagger: { each: 0.03, from: 'start' },
|
|
694
1070
|
}).onPageLoad();
|
|
695
1071
|
```
|
|
696
1072
|
|
|
1073
|
+
### mask
|
|
1074
|
+
|
|
1075
|
+
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.
|
|
1076
|
+
|
|
1077
|
+
```ts
|
|
1078
|
+
Motion('reveal', 'h1', {
|
|
1079
|
+
split: 'lines',
|
|
1080
|
+
mask: true,
|
|
1081
|
+
from: { y: '100%' },
|
|
1082
|
+
stagger: 0.1,
|
|
1083
|
+
}).onPageLoad();
|
|
1084
|
+
```
|
|
1085
|
+
|
|
1086
|
+
### FitConfig
|
|
1087
|
+
|
|
1088
|
+
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.
|
|
1089
|
+
|
|
1090
|
+
```ts
|
|
1091
|
+
interface FitConfig {
|
|
1092
|
+
/** CSS selector for the target element to morph toward */
|
|
1093
|
+
target: string;
|
|
1094
|
+
/** Convert to absolute positioning during animation. Default: false */
|
|
1095
|
+
absolute?: boolean;
|
|
1096
|
+
/** Include scale changes. Default: true */
|
|
1097
|
+
scale?: boolean;
|
|
1098
|
+
/** Animate actual width/height instead of scaleX/scaleY. Default: false */
|
|
1099
|
+
resize?: boolean;
|
|
1100
|
+
}
|
|
1101
|
+
```
|
|
1102
|
+
|
|
1103
|
+
- **`scale: true`** (default) — animates using `scaleX`/`scaleY`. Fast, GPU-accelerated, but can distort text, borders, and box-shadows.
|
|
1104
|
+
- **`resize: true`** — animates actual `width`/`height` properties instead. No visual distortion, but triggers layout reflow. Mutually exclusive with `scale`.
|
|
1105
|
+
|
|
1106
|
+
```ts
|
|
1107
|
+
Motion('reorder', '.container', {
|
|
1108
|
+
fit: { target: '.item', resize: true },
|
|
1109
|
+
duration: 0.5,
|
|
1110
|
+
ease: 'power2.inOut',
|
|
1111
|
+
}).play();
|
|
1112
|
+
```
|
|
1113
|
+
|
|
697
1114
|
---
|
|
698
1115
|
|
|
699
1116
|
## Easing
|
|
@@ -703,23 +1120,22 @@ Easing names are **case-insensitive** strings. Pass them to `AnimationConfig.eas
|
|
|
703
1120
|
| Family | Variants |
|
|
704
1121
|
|--------|----------|
|
|
705
1122
|
| `linear`, `none` | — |
|
|
706
|
-
| `power1` | `power1.in` · `power1.out` · `power1.
|
|
707
|
-
| `power2` | `power2.in` · `power2.out` · `power2.
|
|
708
|
-
| `power3` | `power3.in` · `power3.out` · `power3.
|
|
709
|
-
| `power4` | `power4.in` · `power4.out` · `power4.
|
|
710
|
-
| `sine` | `sine.in` · `sine.out` · `sine.
|
|
711
|
-
| `expo` | `expo.in` · `expo.out` · `expo.
|
|
712
|
-
| `circ` | `circ.in` · `circ.out` · `circ.
|
|
713
|
-
| `back` | `back.in` · `back.out` · `back.
|
|
714
|
-
| `elastic` | `elastic.in` · `elastic.out` · `elastic.
|
|
715
|
-
| `bounce` | `bounce.in` · `bounce.out` · `bounce.
|
|
1123
|
+
| `power1` | `power1.in` · `power1.out` · `power1.inOut` |
|
|
1124
|
+
| `power2` | `power2.in` · `power2.out` · `power2.inOut` |
|
|
1125
|
+
| `power3` | `power3.in` · `power3.out` · `power3.inOut` |
|
|
1126
|
+
| `power4` | `power4.in` · `power4.out` · `power4.inOut` |
|
|
1127
|
+
| `sine` | `sine.in` · `sine.out` · `sine.inOut` |
|
|
1128
|
+
| `expo` | `expo.in` · `expo.out` · `expo.inOut` |
|
|
1129
|
+
| `circ` | `circ.in` · `circ.out` · `circ.inOut` |
|
|
1130
|
+
| `back` | `back.in` · `back.out` · `back.inOut` |
|
|
1131
|
+
| `elastic` | `elastic.in` · `elastic.out` · `elastic.inOut` |
|
|
1132
|
+
| `bounce` | `bounce.in` · `bounce.out` · `bounce.inOut` |
|
|
716
1133
|
|
|
717
1134
|
Unknown strings fall back to `power1.out`.
|
|
718
1135
|
|
|
719
1136
|
```ts
|
|
720
1137
|
Motion('spring-in', '.box', {
|
|
721
1138
|
from: { scale: 0 },
|
|
722
|
-
to: { scale: 1 },
|
|
723
1139
|
duration: 0.8,
|
|
724
1140
|
ease: 'elastic.out',
|
|
725
1141
|
}).play();
|
|
@@ -731,6 +1147,8 @@ Motion('spring-in', '.box', {
|
|
|
731
1147
|
|
|
732
1148
|
All types are re-exported from the package entry point.
|
|
733
1149
|
|
|
1150
|
+
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.
|
|
1151
|
+
|
|
734
1152
|
```ts
|
|
735
1153
|
import type {
|
|
736
1154
|
// Core
|
|
@@ -747,6 +1165,7 @@ import type {
|
|
|
747
1165
|
RepeatConfig,
|
|
748
1166
|
SplitType,
|
|
749
1167
|
PathConfig,
|
|
1168
|
+
FitConfig,
|
|
750
1169
|
|
|
751
1170
|
// Triggers
|
|
752
1171
|
HoverConfig,
|
|
@@ -754,6 +1173,7 @@ import type {
|
|
|
754
1173
|
ScrollConfig,
|
|
755
1174
|
MouseMoveConfig,
|
|
756
1175
|
MarkerConfig,
|
|
1176
|
+
PageExitConfig,
|
|
757
1177
|
|
|
758
1178
|
// Gesture
|
|
759
1179
|
GestureConfig,
|
|
@@ -765,38 +1185,38 @@ import type {
|
|
|
765
1185
|
CursorConfig,
|
|
766
1186
|
CursorStateVars,
|
|
767
1187
|
CursorSqueezeConfig,
|
|
768
|
-
} from '@motion/sdk';
|
|
1188
|
+
} from '@motion.page/sdk';
|
|
769
1189
|
|
|
770
1190
|
// Namespace import
|
|
771
|
-
import { Types } from '@motion/sdk';
|
|
1191
|
+
import { Types } from '@motion.page/sdk';
|
|
772
1192
|
```
|
|
773
1193
|
|
|
774
1194
|
---
|
|
775
1195
|
|
|
776
1196
|
## Browser Build
|
|
777
1197
|
|
|
778
|
-
|
|
1198
|
+
### Browser Build (IIFE)
|
|
1199
|
+
|
|
1200
|
+
An IIFE build script is included but not part of the default build output. To generate a browser bundle:
|
|
1201
|
+
|
|
1202
|
+
```bash
|
|
1203
|
+
bun run packages/sdk/scripts/build-iife.ts
|
|
1204
|
+
```
|
|
1205
|
+
|
|
1206
|
+
This creates a self-contained script that exposes `window.Motion` and `window.MotionTimeline`:
|
|
779
1207
|
|
|
780
1208
|
```html
|
|
781
1209
|
<script src="motion-sdk.browser.js"></script>
|
|
782
1210
|
<script>
|
|
783
|
-
// Globals are exposed on window:
|
|
784
1211
|
const { Motion, MotionTimeline } = window;
|
|
785
1212
|
|
|
786
|
-
Motion('fade
|
|
787
|
-
from: { opacity: 0 },
|
|
788
|
-
|
|
789
|
-
duration: 1,
|
|
1213
|
+
Motion('fade', '.hero', {
|
|
1214
|
+
from: { opacity: 0, y: 30 },
|
|
1215
|
+
duration: 0.8,
|
|
790
1216
|
}).onPageLoad();
|
|
791
1217
|
</script>
|
|
792
1218
|
```
|
|
793
1219
|
|
|
794
|
-
To regenerate the browser bundle:
|
|
795
|
-
|
|
796
|
-
```bash
|
|
797
|
-
bun run packages/sdk/scripts/build-iife.ts
|
|
798
|
-
```
|
|
799
|
-
|
|
800
1220
|
---
|
|
801
1221
|
|
|
802
1222
|
## Browser Support
|
|
@@ -814,4 +1234,12 @@ Modern evergreen browsers:
|
|
|
814
1234
|
|
|
815
1235
|
## License
|
|
816
1236
|
|
|
817
|
-
|
|
1237
|
+
**FSL-1.1-Apache-2.0** — [Functional Source License, Version 1.1, Apache 2.0 Future License](./LICENSE)
|
|
1238
|
+
|
|
1239
|
+
**TL;DR:** Free for everyone. Use it in your websites, apps, SaaS products, client projects — commercial or not. No restrictions.
|
|
1240
|
+
|
|
1241
|
+
**The one exception:** You cannot use this SDK to build a product that competes with Motion.page (e.g., a no-code animation builder, visual animation editor, or similar tool). If you're building something like that, [contact us](mailto:hello@motion.page) for an enterprise license.
|
|
1242
|
+
|
|
1243
|
+
After 2 years from each release, the code converts to the [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) license with no restrictions at all.
|
|
1244
|
+
|
|
1245
|
+
See [LICENSE](./LICENSE) for the full legal text.
|