@motion.page/sdk 0.1.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/README.md +817 -0
- package/dist/core/Animation.d.ts +230 -0
- package/dist/core/AnimationBuilder.d.ts +125 -0
- package/dist/core/Engine.d.ts +140 -0
- package/dist/core/Motion.d.ts +56 -0
- package/dist/core/PropTween.d.ts +81 -0
- package/dist/core/Ticker.d.ts +107 -0
- package/dist/core/Timeline.d.ts +294 -0
- package/dist/easing/index.d.ts +65 -0
- package/dist/fit/FitResolver.d.ts +12 -0
- package/dist/index.cjs +34 -0
- package/dist/index.cjs.map +52 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +52 -0
- package/dist/memory/AnimationPool.d.ts +14 -0
- package/dist/memory/ObjectPool.d.ts +37 -0
- package/dist/memory/PropTweenPool.d.ts +14 -0
- package/dist/registries/SDKRegistry.d.ts +69 -0
- package/dist/render/CSSRenderer.d.ts +33 -0
- package/dist/render/PathRenderer.d.ts +24 -0
- package/dist/render/RenderBatch.d.ts +27 -0
- package/dist/render/TransformCache.d.ts +54 -0
- package/dist/stagger/StaggerResolver.d.ts +28 -0
- package/dist/triggers/BaseTrigger.d.ts +61 -0
- package/dist/triggers/CursorTrigger.d.ts +86 -0
- package/dist/triggers/EventTrigger.d.ts +48 -0
- package/dist/triggers/GestureTrigger.d.ts +137 -0
- package/dist/triggers/MarkerManager.d.ts +42 -0
- package/dist/triggers/MouseMoveTrigger.d.ts +49 -0
- package/dist/triggers/PageExitTrigger.d.ts +74 -0
- package/dist/triggers/PageLoadTrigger.d.ts +37 -0
- package/dist/triggers/PinManager.d.ts +131 -0
- package/dist/triggers/ScrollTrigger.d.ts +103 -0
- package/dist/triggers/TriggerManager.d.ts +133 -0
- package/dist/types/index.d.ts +355 -0
- package/dist/types/public.d.ts +7 -0
- package/dist/utils/ColorParser.d.ts +23 -0
- package/dist/utils/DrawSVGParser.d.ts +62 -0
- package/dist/utils/FilterParser.d.ts +49 -0
- package/dist/utils/MotionUtils.d.ts +136 -0
- package/dist/utils/PathParser.d.ts +89 -0
- package/dist/utils/PositionParser.d.ts +24 -0
- package/dist/utils/PropertyParser.d.ts +159 -0
- package/dist/utils/QuickSetter.d.ts +16 -0
- package/dist/utils/ScrollPositionParser.d.ts +19 -0
- package/dist/utils/StyleReset.d.ts +73 -0
- package/dist/utils/TargetResolver.d.ts +23 -0
- package/dist/utils/TextSplitter.d.ts +90 -0
- package/dist/utils/executeTimelineAction.d.ts +18 -0
- package/dist/utils/getLayoutRect.d.ts +10 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,817 @@
|
|
|
1
|
+
# @motion/sdk
|
|
2
|
+
|
|
3
|
+
A high-performance CSS animation SDK with a declarative, config-based API. Zero runtime dependencies.
|
|
4
|
+
|
|
5
|
+
  
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Installation](#installation)
|
|
12
|
+
- [Quick Start](#quick-start)
|
|
13
|
+
- [Core Concept](#core-concept)
|
|
14
|
+
- [API Reference](#api-reference)
|
|
15
|
+
- [Motion()](#motion-function)
|
|
16
|
+
- [Motion Static Methods](#motion-static-methods)
|
|
17
|
+
- [Motion.utils](#motionutils)
|
|
18
|
+
- [Timeline](#timeline)
|
|
19
|
+
- [Triggers](#triggers)
|
|
20
|
+
- [Lifecycle Callbacks](#lifecycle-callbacks)
|
|
21
|
+
- [AnimationConfig](#animationconfig)
|
|
22
|
+
- [Easing](#easing)
|
|
23
|
+
- [Types](#types)
|
|
24
|
+
- [Browser Build](#browser-build)
|
|
25
|
+
- [Browser Support](#browser-support)
|
|
26
|
+
- [License](#license)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Not published to npm — used internally within the Motion.page monorepo.
|
|
34
|
+
# The package name is @motion/sdk
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
For direct browser use, see [Browser Build](#browser-build).
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
### Basic Animation
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { Motion } from '@motion/sdk';
|
|
47
|
+
|
|
48
|
+
// Fade in and slide up
|
|
49
|
+
Motion('hero-intro', '#hero', {
|
|
50
|
+
from: { opacity: 0, y: 50 },
|
|
51
|
+
to: { opacity: 1, y: 0 },
|
|
52
|
+
duration: 0.8,
|
|
53
|
+
ease: 'power2.out',
|
|
54
|
+
}).play();
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Scroll-Triggered Animation
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
// Scrub animation progress to scroll position
|
|
61
|
+
Motion('scroll-reveal', '.card', {
|
|
62
|
+
from: { opacity: 0, y: 40 },
|
|
63
|
+
to: { opacity: 1, y: 0 },
|
|
64
|
+
duration: 0.6,
|
|
65
|
+
}).onScroll({ scrub: true, start: 'top 80%', end: 'top 30%' });
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Hover Effect
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
// Play on hover, reverse on leave
|
|
72
|
+
Motion('btn-hover', '.btn', {
|
|
73
|
+
to: { scale: 1.05, backgroundColor: '#0099ff' },
|
|
74
|
+
duration: 0.3,
|
|
75
|
+
ease: 'power2.out',
|
|
76
|
+
}).onHover({ onLeave: 'reverse' });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Multi-Step Timeline with Stagger
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
// Sequence multiple targets; each entry has its own target and position
|
|
83
|
+
Motion('intro-sequence', [
|
|
84
|
+
{
|
|
85
|
+
target: '.title',
|
|
86
|
+
from: { opacity: 0, y: -30 },
|
|
87
|
+
to: { opacity: 1, y: 0 },
|
|
88
|
+
duration: 0.6,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
target: '.cards',
|
|
92
|
+
from: { opacity: 0, y: 20 },
|
|
93
|
+
to: { opacity: 1, y: 0 },
|
|
94
|
+
duration: 0.5,
|
|
95
|
+
stagger: { each: 0.1, from: 'start' },
|
|
96
|
+
position: '+=0.1', // starts 0.1s after the previous entry ends
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
target: '.cta',
|
|
100
|
+
from: { opacity: 0, scale: 0.9 },
|
|
101
|
+
to: { opacity: 1, scale: 1 },
|
|
102
|
+
duration: 0.4,
|
|
103
|
+
position: '<', // starts at the same time as the previous entry
|
|
104
|
+
},
|
|
105
|
+
]).onPageLoad();
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Replay an Existing Timeline
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
// Retrieve a previously created timeline by name and replay it
|
|
112
|
+
Motion('hero-intro').restart();
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Core Concept
|
|
118
|
+
|
|
119
|
+
Every animation in the SDK is a **named timeline**. The name is the first argument to `Motion()` and acts as a registry key — calling `Motion('same-name')` always returns the same `Timeline` instance.
|
|
120
|
+
|
|
121
|
+
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
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## API Reference
|
|
126
|
+
|
|
127
|
+
### Motion Function
|
|
128
|
+
|
|
129
|
+
Three overloads:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
// 1. Retrieve an existing named timeline
|
|
133
|
+
Motion(name: string): Timeline
|
|
134
|
+
|
|
135
|
+
// 2. Create a single-animation timeline
|
|
136
|
+
Motion(name: string, target: TargetInput, config: AnimationConfig): Timeline
|
|
137
|
+
|
|
138
|
+
// 3. Create a multi-step timeline from an array of entries
|
|
139
|
+
Motion(name: string, animations: AnimationEntry[]): Timeline
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**`TargetInput`** accepts: CSS selector string, `string[]`, `Element`, `NodeList`, `Element[]`, or a plain object / array of plain objects (for object tweening).
|
|
143
|
+
|
|
144
|
+
Calling `Motion()` with the same name on an already-existing timeline returns it unchanged (the retrieve overload). To rebuild a timeline, call `.kill()` on the old one first.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
### Motion Static Methods
|
|
149
|
+
|
|
150
|
+
| Method | Signature | Description |
|
|
151
|
+
|--------|-----------|-------------|
|
|
152
|
+
| `Motion.set` | `(target: TargetInput, vars: AnimationVars): void` | Immediately apply CSS / transform properties with no animation (like `gsap.set`). Values are written synchronously. |
|
|
153
|
+
| `Motion.get` | `(name: string): Timeline \| undefined` | Get a timeline by name; returns `undefined` if none exists. |
|
|
154
|
+
| `Motion.has` | `(name: string): boolean` | Check whether a named timeline is registered. |
|
|
155
|
+
| `Motion.getNames` | `(): string[]` | Return the names of all registered timelines. |
|
|
156
|
+
| `Motion.kill` | `(name: string): void` | Kill a single timeline by name. Restores initial CSS. |
|
|
157
|
+
| `Motion.killAll` | `(): void` | Kill every timeline and animation managed by the engine. |
|
|
158
|
+
| `Motion.reset` | `(targets: TargetInput): void` | Kill animations targeting those elements, revert text splits, clear transform cache and inline animation styles. |
|
|
159
|
+
| `Motion.refreshScrollTriggers` | `(): void` | Recalculate all scroll trigger start/end positions (call after layout changes). |
|
|
160
|
+
| `Motion.cleanup` | `(): void` | Remove ScrollTrigger spacer and marker DOM nodes from the document. |
|
|
161
|
+
|
|
162
|
+
#### Examples
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
// Immediately hide an element before animating it in
|
|
166
|
+
Motion.set('#hero', { opacity: 0, y: -20 });
|
|
167
|
+
|
|
168
|
+
// Check and conditionally replay
|
|
169
|
+
if (Motion.has('hero-intro')) {
|
|
170
|
+
Motion('hero-intro').restart();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Tear down everything (e.g., on page navigation)
|
|
174
|
+
Motion.killAll();
|
|
175
|
+
Motion.cleanup();
|
|
176
|
+
|
|
177
|
+
// Reset a specific element to its original state
|
|
178
|
+
Motion.reset('.animated-card');
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
### Motion.utils
|
|
184
|
+
|
|
185
|
+
GSAP-compatible utility functions accessible via `Motion.utils`. These are drop-in replacements for `gsap.utils.*` helpers.
|
|
186
|
+
|
|
187
|
+
| Method | Signature | Description |
|
|
188
|
+
|--------|-----------|-------------|
|
|
189
|
+
| `toArray` | `(target, scope?) → Element[]` | Convert CSS selector, NodeList, HTMLCollection, or Element to a flat array. Drop-in for `gsap.utils.toArray()`. |
|
|
190
|
+
| `clamp` | `(min, max, value?) → number \| fn` | Clamp a value between min and max. Curried if value omitted. |
|
|
191
|
+
| `random` | `(min, max, snap?) → number` | Random number between min and max. Optional snap increment. |
|
|
192
|
+
| `snap` | `(snapTo, value?) → number \| fn` | Snap to nearest increment or array value. Curried if value omitted. |
|
|
193
|
+
| `interpolate` | `(start, end, progress) → number` | Linear interpolation (lerp) between two values. |
|
|
194
|
+
| `mapRange` | `(inMin, inMax, outMin, outMax, value?) → number \| fn` | Map a value from one range to another. Curried if value omitted. |
|
|
195
|
+
| `normalize` | `(min, max, value?) → number \| fn` | Normalize a value to 0–1 within a range. Curried if value omitted. |
|
|
196
|
+
| `wrap` | `(min, max, value?) → number \| fn` | Wrap a value within a range (modular arithmetic). Curried if value omitted. |
|
|
197
|
+
|
|
198
|
+
#### Examples
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
// Convert selector to array (replaces gsap.utils.toArray)
|
|
202
|
+
const sections = Motion.utils.toArray('.section');
|
|
203
|
+
console.log(sections.length); // number of matching elements
|
|
204
|
+
|
|
205
|
+
// Clamp
|
|
206
|
+
Motion.utils.clamp(0, 100, 150); // 100
|
|
207
|
+
const clamp01 = Motion.utils.clamp(0, 1);
|
|
208
|
+
clamp01(1.5); // 1
|
|
209
|
+
|
|
210
|
+
// Snap to increment
|
|
211
|
+
Motion.utils.snap(5, 13); // 15
|
|
212
|
+
Motion.utils.snap([0, 25, 50], 30); // 25
|
|
213
|
+
|
|
214
|
+
// Random with snap
|
|
215
|
+
Motion.utils.random(0, 100, 10); // 0, 10, 20, ..., 100
|
|
216
|
+
|
|
217
|
+
// Map between ranges
|
|
218
|
+
Motion.utils.mapRange(0, 100, 0, 1, 50); // 0.5
|
|
219
|
+
|
|
220
|
+
// Normalize to 0–1
|
|
221
|
+
Motion.utils.normalize(0, 255, 128); // ~0.502
|
|
222
|
+
|
|
223
|
+
// Wrap (modular)
|
|
224
|
+
Motion.utils.wrap(0, 360, 450); // 90
|
|
225
|
+
|
|
226
|
+
// Interpolate (lerp)
|
|
227
|
+
Motion.utils.interpolate(0, 100, 0.5); // 50
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
#### Migration from GSAP
|
|
231
|
+
|
|
232
|
+
| GSAP | Motion SDK |
|
|
233
|
+
|------|------------|
|
|
234
|
+
| `gsap.utils.toArray('.el')` | `Motion.utils.toArray('.el')` |
|
|
235
|
+
| `gsap.utils.clamp(0, 1, v)` | `Motion.utils.clamp(0, 1, v)` |
|
|
236
|
+
| `gsap.utils.random(0, 100)` | `Motion.utils.random(0, 100)` |
|
|
237
|
+
| `gsap.utils.snap(5, v)` | `Motion.utils.snap(5, v)` |
|
|
238
|
+
| `gsap.utils.mapRange(0, 1, 0, 100, v)` | `Motion.utils.mapRange(0, 1, 0, 100, v)` |
|
|
239
|
+
| `gsap.utils.normalize(0, 100, v)` | `Motion.utils.normalize(0, 100, v)` |
|
|
240
|
+
| `gsap.utils.wrap(0, 360, v)` | `Motion.utils.wrap(0, 360, v)` |
|
|
241
|
+
| `gsap.utils.interpolate(0, 100, 0.5)` | `Motion.utils.interpolate(0, 100, 0.5)` |
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
### Timeline
|
|
246
|
+
|
|
247
|
+
`Timeline` is the object returned by every `Motion()` call. All playback and trigger methods return `this` for chaining.
|
|
248
|
+
|
|
249
|
+
#### Playback Control
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
tl.play(from?: number): this // Play forward; optional start time in seconds
|
|
253
|
+
tl.pause(atTime?: number): this // Pause; optional snap-to time
|
|
254
|
+
tl.reverse(from?: number): this // Play backward; optional start time
|
|
255
|
+
tl.restart(): this // Seek to t=0, restore initial CSS, then play forward
|
|
256
|
+
tl.seek(position: number): this // Jump to a time (seconds) without playing
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
#### State — Getter / Setter
|
|
260
|
+
|
|
261
|
+
Calling with no argument reads the value; calling with an argument sets it and returns `this`.
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
tl.duration(): number // Read total duration in seconds
|
|
265
|
+
tl.progress(value?: number): number | this // Get or set normalized progress (0–1)
|
|
266
|
+
tl.time(value?: number): number | this // Get or set current time in seconds
|
|
267
|
+
tl.timeScale(value?: number): number | this // Get or set playback speed multiplier
|
|
268
|
+
tl.isActive(): boolean // Is the timeline currently animating?
|
|
269
|
+
tl.getName(): string | undefined // Registered name, or undefined for anonymous
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
// Read
|
|
274
|
+
const p = tl.progress(); // e.g. 0.5
|
|
275
|
+
|
|
276
|
+
// Write (chainable)
|
|
277
|
+
tl.progress(0.5).play();
|
|
278
|
+
tl.timeScale(2).restart(); // play at 2× speed
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
#### Inserting Callbacks at a Position
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
tl.call(
|
|
285
|
+
callback: (...args: unknown[]) => void,
|
|
286
|
+
params?: unknown[],
|
|
287
|
+
position?: string | number,
|
|
288
|
+
): this
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**Position syntax** (same rules apply to `AnimationEntry.position`):
|
|
292
|
+
|
|
293
|
+
| Value | Meaning |
|
|
294
|
+
|-------|---------|
|
|
295
|
+
| `0.5` | 0.5 s from start (absolute) |
|
|
296
|
+
| `"+=0.5"` | 0.5 s after the previous entry ends |
|
|
297
|
+
| `"-=0.3"` | 0.3 s before the previous entry ends |
|
|
298
|
+
| `"<"` | At the same start time as the previous entry |
|
|
299
|
+
| `">"` | Immediately after the previous entry ends |
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
Motion('demo', '.box', { from: { opacity: 0 }, to: { opacity: 1 }, duration: 1 })
|
|
303
|
+
.call(() => console.log('halfway'), [], 0.5)
|
|
304
|
+
.call(() => console.log('done'), [], '>');
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
#### Cleanup
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
tl.kill(clearProps?: boolean): void
|
|
311
|
+
// Destroy the timeline. clearProps=true (default) restores initial CSS on all targets.
|
|
312
|
+
|
|
313
|
+
tl.clear(): this
|
|
314
|
+
// Reset and rebuild the timeline without destroying it.
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
### Triggers
|
|
320
|
+
|
|
321
|
+
All trigger methods are chainable and attach behaviour to the timeline without requiring you to manage event listeners manually.
|
|
322
|
+
|
|
323
|
+
#### `.onHover(config?)`
|
|
324
|
+
|
|
325
|
+
Play on `mouseenter`, react on `mouseleave`.
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
interface HoverConfig {
|
|
329
|
+
target?: string | Element; // Defaults to animation target(s)
|
|
330
|
+
each?: boolean; // Apply trigger to each matched element individually
|
|
331
|
+
onLeave?: 'reverse' | 'pause' | 'stop' | 'restart' | 'none';
|
|
332
|
+
leaveDelay?: number; // Seconds to wait before triggering onLeave
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
```ts
|
|
337
|
+
Motion('card-hover', '.card', {
|
|
338
|
+
to: { y: -8, boxShadow: '0 12px 24px rgba(0,0,0,0.15)' },
|
|
339
|
+
duration: 0.3,
|
|
340
|
+
ease: 'power2.out',
|
|
341
|
+
}).onHover({ each: true, onLeave: 'reverse' });
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
#### `.onClick(config?)`
|
|
345
|
+
|
|
346
|
+
Toggle animation on click.
|
|
347
|
+
|
|
348
|
+
```ts
|
|
349
|
+
interface ClickConfig {
|
|
350
|
+
target?: string | Element;
|
|
351
|
+
each?: boolean;
|
|
352
|
+
secondTarget?: string | Element; // Alternative click target
|
|
353
|
+
toggle?: 'reverse' | 'restart' | 'play';
|
|
354
|
+
preventDefault?: boolean;
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
```ts
|
|
359
|
+
Motion('menu-toggle', '#menu', {
|
|
360
|
+
from: { height: 0, opacity: 0 },
|
|
361
|
+
to: { height: 'auto', opacity: 1 },
|
|
362
|
+
duration: 0.4,
|
|
363
|
+
ease: 'power2.inOut',
|
|
364
|
+
}).onClick({ target: '#menu-btn', toggle: 'reverse' });
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
#### `.onScroll(config?)`
|
|
368
|
+
|
|
369
|
+
Scrub or snap animation to scroll position.
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
interface ScrollConfig {
|
|
373
|
+
target?: string | Element;
|
|
374
|
+
start?: string; // e.g. 'top 80%'
|
|
375
|
+
end?: string; // e.g. 'bottom 20%'
|
|
376
|
+
scrub?: boolean | number; // true = instant, number = smoothing seconds
|
|
377
|
+
snap?: number | number[] | ((progress: number) => number); // Snap scroll progress
|
|
378
|
+
markers?: boolean | MarkerConfig; // Debug markers
|
|
379
|
+
scroller?: string | Element; // Custom scroll container
|
|
380
|
+
pin?: boolean | string; // Pin the element during scroll
|
|
381
|
+
pinSpacing?: boolean | 'margin' | 'padding';
|
|
382
|
+
each?: boolean;
|
|
383
|
+
toggleActions?: string; // e.g. 'play none none reverse'
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
**`snap`** controls how scroll progress snaps to discrete values:
|
|
388
|
+
|
|
389
|
+
- **Number** — fractional increment to snap to. E.g. `snap: 0.25` snaps to 0, 0.25, 0.5, 0.75, 1.
|
|
390
|
+
- **Array** — explicit progress values to snap to. E.g. `snap: [0, 0.33, 0.66, 1]`.
|
|
391
|
+
- **Function** — custom snap logic. Receives raw progress (0–1), returns snapped value.
|
|
392
|
+
|
|
393
|
+
Common pattern for horizontal scroll with equal sections:
|
|
394
|
+
|
|
395
|
+
```ts
|
|
396
|
+
const sections = Motion.utils.toArray('.section');
|
|
397
|
+
Motion('h-scroll', '.panel', {
|
|
398
|
+
to: { x: `-${(sections.length - 1) * 100}%` },
|
|
399
|
+
duration: 1,
|
|
400
|
+
}).onScroll({
|
|
401
|
+
scrub: true,
|
|
402
|
+
snap: 1 / (sections.length - 1),
|
|
403
|
+
pin: true,
|
|
404
|
+
start: 'top top',
|
|
405
|
+
end: `+=${sections.length * 100}%`,
|
|
406
|
+
});
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
```ts
|
|
410
|
+
Motion('parallax', '.hero-bg', {
|
|
411
|
+
from: { y: 0 },
|
|
412
|
+
to: { y: -100 },
|
|
413
|
+
}).onScroll({ scrub: 1, start: 'top top', end: 'bottom top' });
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
#### `.onMouseMove(config?)`
|
|
417
|
+
|
|
418
|
+
Drive animation progress from mouse position.
|
|
419
|
+
|
|
420
|
+
```ts
|
|
421
|
+
interface MouseMoveConfig {
|
|
422
|
+
type?: 'distance' | 'axis';
|
|
423
|
+
target?: string | Element; // Element whose bounds define the movement area
|
|
424
|
+
each?: boolean;
|
|
425
|
+
smooth?: number; // Smoothing factor (higher = slower follow)
|
|
426
|
+
startProgress?: number; // Progress value at rest (0–1)
|
|
427
|
+
leaveProgress?: number; // Progress to animate to on mouse leave
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
```ts
|
|
432
|
+
Motion('parallax-depth', '.layer', {
|
|
433
|
+
from: { x: -20, y: -20 },
|
|
434
|
+
to: { x: 20, y: 20 },
|
|
435
|
+
axis: 'x', // bind to horizontal axis only
|
|
436
|
+
}).onMouseMove({ type: 'axis', smooth: 0.1 });
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
#### `.onPageLoad()`
|
|
440
|
+
|
|
441
|
+
Play the animation automatically when the page finishes loading.
|
|
442
|
+
|
|
443
|
+
```ts
|
|
444
|
+
Motion('page-intro', [
|
|
445
|
+
{ target: '.logo', from: { opacity: 0 }, to: { opacity: 1 }, duration: 0.5 },
|
|
446
|
+
{ target: '.nav', from: { y: -20, opacity: 0 }, to: { y: 0, opacity: 1 }, duration: 0.4 },
|
|
447
|
+
]).onPageLoad();
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
#### `.onGesture(config)`
|
|
451
|
+
|
|
452
|
+
Respond to pointer, touch, wheel, or scroll gestures with fine-grained event-to-action mapping.
|
|
453
|
+
|
|
454
|
+
```ts
|
|
455
|
+
type GestureInputType = 'pointer' | 'touch' | 'wheel' | 'scroll';
|
|
456
|
+
|
|
457
|
+
type GestureEvent =
|
|
458
|
+
| 'Up' | 'Down' | 'Left' | 'Right'
|
|
459
|
+
| 'UpComplete' | 'DownComplete' | 'LeftComplete' | 'RightComplete'
|
|
460
|
+
| 'Change' | 'ChangeX' | 'ChangeY'
|
|
461
|
+
| 'ToggleX' | 'ToggleY'
|
|
462
|
+
| 'Press' | 'Release' | 'PressInit'
|
|
463
|
+
| 'Drag' | 'DragEnd'
|
|
464
|
+
| 'Stop' | 'Hover' | 'HoverEnd';
|
|
465
|
+
|
|
466
|
+
type GestureAction =
|
|
467
|
+
| 'play' | 'pause' | 'reverse' | 'restart' | 'toggle' | 'reset' | 'complete' | 'kill'
|
|
468
|
+
| 'playReverse' | 'progressUp' | 'progressDown' | 'playNext' | 'playPrevious';
|
|
469
|
+
|
|
470
|
+
interface GestureConfig {
|
|
471
|
+
target?: string | Element;
|
|
472
|
+
types: GestureInputType[];
|
|
473
|
+
events: Partial<Record<GestureEvent, GestureAction>>;
|
|
474
|
+
tolerance?: number;
|
|
475
|
+
dragMinimum?: number;
|
|
476
|
+
wheelSpeed?: number;
|
|
477
|
+
scrollSpeed?: number;
|
|
478
|
+
preventDefault?: boolean;
|
|
479
|
+
lockAxis?: boolean;
|
|
480
|
+
each?: boolean;
|
|
481
|
+
stopDelay?: number;
|
|
482
|
+
animationStep?: number | Partial<Record<GestureEvent, number>>;
|
|
483
|
+
smooth?: number;
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
```ts
|
|
488
|
+
Motion('swipe-gallery', '.gallery', {
|
|
489
|
+
from: { x: 0 },
|
|
490
|
+
to: { x: -100 },
|
|
491
|
+
}).onGesture({
|
|
492
|
+
types: ['pointer', 'touch'],
|
|
493
|
+
events: {
|
|
494
|
+
Left: 'playNext',
|
|
495
|
+
Right: 'playPrevious',
|
|
496
|
+
},
|
|
497
|
+
dragMinimum: 40,
|
|
498
|
+
lockAxis: true,
|
|
499
|
+
});
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
#### `.onCursor(config)`
|
|
503
|
+
|
|
504
|
+
Replace the native cursor with a fully animated custom cursor.
|
|
505
|
+
|
|
506
|
+
```ts
|
|
507
|
+
interface CursorConfig {
|
|
508
|
+
target?: string | Element;
|
|
509
|
+
type?: 'basic' | 'text' | 'media';
|
|
510
|
+
smooth?: number;
|
|
511
|
+
squeeze?: boolean | { min?: number; max?: number; multiplier?: number };
|
|
512
|
+
hideNative?: boolean;
|
|
513
|
+
default: CursorStateVars; // Required: default cursor appearance
|
|
514
|
+
hover?: CursorStateVars; // State when hovering interactive elements
|
|
515
|
+
click?: CursorStateVars; // State while pressing
|
|
516
|
+
text?: Record<string, string | number>;
|
|
517
|
+
media?: Record<string, string | number>;
|
|
518
|
+
}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
```ts
|
|
522
|
+
Motion('custom-cursor', 'body', {
|
|
523
|
+
to: { opacity: 1 },
|
|
524
|
+
duration: 0,
|
|
525
|
+
}).onCursor({
|
|
526
|
+
target: '#cursor-dot',
|
|
527
|
+
smooth: 0.08,
|
|
528
|
+
hideNative: true,
|
|
529
|
+
default: { width: 12, height: 12, borderRadius: '50%', backgroundColor: '#fff' },
|
|
530
|
+
hover: { width: 40, height: 40, backgroundColor: 'transparent', borderColor: '#fff' },
|
|
531
|
+
click: { scale: 0.8 },
|
|
532
|
+
});
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
---
|
|
536
|
+
|
|
537
|
+
### Lifecycle Callbacks
|
|
538
|
+
|
|
539
|
+
Attach callbacks via the `AnimationConfig` object or directly on the `Timeline` instance. Both approaches are chainable.
|
|
540
|
+
|
|
541
|
+
#### Via `AnimationConfig`
|
|
542
|
+
|
|
543
|
+
```ts
|
|
544
|
+
Motion('slide-in', '.card', {
|
|
545
|
+
from: { opacity: 0, x: -40 },
|
|
546
|
+
to: { opacity: 1, x: 0 },
|
|
547
|
+
duration: 0.6,
|
|
548
|
+
onStart: () => console.log('started'),
|
|
549
|
+
onUpdate: (progress) => console.log('progress:', progress),
|
|
550
|
+
onComplete: () => console.log('done'),
|
|
551
|
+
onRepeat: (count) => console.log('repeat #', count),
|
|
552
|
+
onReverseComplete: () => console.log('reversed'),
|
|
553
|
+
repeat: { times: 2, yoyo: true, delay: 0.5 },
|
|
554
|
+
});
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
#### Via `Timeline` Methods
|
|
558
|
+
|
|
559
|
+
```ts
|
|
560
|
+
Motion('slide-in', '.card', { from: { opacity: 0 }, to: { opacity: 1 }, duration: 0.6 })
|
|
561
|
+
.onStart(() => console.log('started'))
|
|
562
|
+
.onUpdate((progress, time) => console.log(progress, time))
|
|
563
|
+
.onComplete(() => console.log('done'));
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
| Callback | Timeline method | AnimationConfig field | Arguments |
|
|
567
|
+
|----------|-----------------|-----------------------|-----------|
|
|
568
|
+
| Start | `.onStart(cb)` | `onStart` | none |
|
|
569
|
+
| Update | `.onUpdate(cb)` | `onUpdate` | `(progress: number, time: number)` via method; `(progress: number)` via config |
|
|
570
|
+
| Complete | `.onComplete(cb)` | `onComplete` | none |
|
|
571
|
+
| Repeat | — | `onRepeat` | `(repeatCount: number)` |
|
|
572
|
+
| Reverse complete | — | `onReverseComplete` | none |
|
|
573
|
+
|
|
574
|
+
---
|
|
575
|
+
|
|
576
|
+
## AnimationConfig
|
|
577
|
+
|
|
578
|
+
Full shape of the config object used for both single-animation (`Motion(name, target, config)`) and each entry in a multi-step timeline (`AnimationEntry`).
|
|
579
|
+
|
|
580
|
+
```ts
|
|
581
|
+
interface AnimationConfig {
|
|
582
|
+
from?: AnimationVars; // Initial values (animated FROM these)
|
|
583
|
+
to?: AnimationVars; // Target values (animated TO these)
|
|
584
|
+
duration?: number; // Seconds (default: engine default)
|
|
585
|
+
delay?: number; // Seconds before animation begins
|
|
586
|
+
ease?: string; // Easing name string, see Easing section
|
|
587
|
+
stagger?: number | StaggerVars; // Per-element stagger delay
|
|
588
|
+
repeat?: RepeatConfig; // Loop/yoyo configuration
|
|
589
|
+
split?: SplitType; // Text splitting for per-char/word/line animation
|
|
590
|
+
axis?: 'x' | 'y'; // Axis binding for onMouseMove animations
|
|
591
|
+
|
|
592
|
+
// Lifecycle
|
|
593
|
+
onStart?: () => void;
|
|
594
|
+
onUpdate?: (progress: number) => void;
|
|
595
|
+
onComplete?: () => void;
|
|
596
|
+
onRepeat?: (repeatCount: number) => void;
|
|
597
|
+
onReverseComplete?: () => void;
|
|
598
|
+
}
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
### AnimationVars
|
|
602
|
+
|
|
603
|
+
All animatable properties:
|
|
604
|
+
|
|
605
|
+
```ts
|
|
606
|
+
// Transforms
|
|
607
|
+
x, y, z // number | string (px default)
|
|
608
|
+
rotate, rotateX, rotateY, rotateZ // number | string (deg default)
|
|
609
|
+
scale, scaleX, scaleY, scaleZ // number
|
|
610
|
+
skewX, skewY // number | string
|
|
611
|
+
perspective // number | string
|
|
612
|
+
|
|
613
|
+
// Visual
|
|
614
|
+
opacity // number (0–1)
|
|
615
|
+
backgroundColor, color // string (CSS color)
|
|
616
|
+
filter // string (CSS filter)
|
|
617
|
+
transformOrigin // string
|
|
618
|
+
|
|
619
|
+
// Layout
|
|
620
|
+
width, height // number | string
|
|
621
|
+
top, left, right, bottom // number | string
|
|
622
|
+
margin, marginTop, marginRight, marginBottom, marginLeft
|
|
623
|
+
padding, paddingTop, paddingRight, paddingBottom, paddingLeft
|
|
624
|
+
|
|
625
|
+
// Typography
|
|
626
|
+
fontSize, lineHeight, letterSpacing // number | string
|
|
627
|
+
borderRadius // number | string
|
|
628
|
+
zIndex // number
|
|
629
|
+
backgroundPosition // string
|
|
630
|
+
|
|
631
|
+
// Border / outline colors
|
|
632
|
+
borderColor, borderTopColor, borderRightColor, borderBottomColor, borderLeftColor
|
|
633
|
+
outlineColor, textDecorationColor, caretColor
|
|
634
|
+
|
|
635
|
+
// SVG
|
|
636
|
+
fill, stroke // string (CSS color)
|
|
637
|
+
drawSVG // string | { start?: number; end?: number }
|
|
638
|
+
|
|
639
|
+
// Motion path
|
|
640
|
+
path: {
|
|
641
|
+
target: string | Element; // SVG <path> selector or element
|
|
642
|
+
align?: string | Element; // Align bounding box to this element
|
|
643
|
+
alignAt?: [number, number]; // Origin point [x%, y%], default [50, 50]
|
|
644
|
+
start?: number; // Path start (0–1), default 0
|
|
645
|
+
end?: number; // Path end (0–1), default 1
|
|
646
|
+
rotate?: boolean; // Auto-rotate along tangent
|
|
647
|
+
}
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
### StaggerVars
|
|
651
|
+
|
|
652
|
+
```ts
|
|
653
|
+
interface StaggerVars {
|
|
654
|
+
each?: number; // Seconds between each element
|
|
655
|
+
amount?: number; // Total stagger spread (alternative to each)
|
|
656
|
+
from?: 'start' | 'center' | 'edges' | 'random' | 'end' | number;
|
|
657
|
+
grid?: 'auto' | [number, number]; // 2D grid stagger
|
|
658
|
+
axis?: 'x' | 'y'; // Grid stagger axis
|
|
659
|
+
ease?: string; // Easing applied to the stagger distribution
|
|
660
|
+
}
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
### RepeatConfig
|
|
664
|
+
|
|
665
|
+
```ts
|
|
666
|
+
interface RepeatConfig {
|
|
667
|
+
times: number; // Number of additional repetitions (-1 = infinite)
|
|
668
|
+
delay?: number; // Seconds between repetitions
|
|
669
|
+
yoyo?: boolean; // Alternate direction each cycle
|
|
670
|
+
}
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
### SplitType
|
|
674
|
+
|
|
675
|
+
```ts
|
|
676
|
+
type SplitType =
|
|
677
|
+
| 'chars'
|
|
678
|
+
| 'words'
|
|
679
|
+
| 'lines'
|
|
680
|
+
| 'chars,words'
|
|
681
|
+
| 'words,lines'
|
|
682
|
+
| 'chars,words,lines';
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
Text is split into wrapper `<span>` elements before animating. `Motion.reset()` reverts the DOM.
|
|
686
|
+
|
|
687
|
+
```ts
|
|
688
|
+
Motion('text-reveal', '.headline', {
|
|
689
|
+
from: { opacity: 0, y: 20 },
|
|
690
|
+
to: { opacity: 1, y: 0 },
|
|
691
|
+
duration: 0.5,
|
|
692
|
+
split: 'chars',
|
|
693
|
+
stagger: { each: 0.03, from: 'start' },
|
|
694
|
+
}).onPageLoad();
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
---
|
|
698
|
+
|
|
699
|
+
## Easing
|
|
700
|
+
|
|
701
|
+
Easing names are **case-insensitive** strings. Pass them to `AnimationConfig.ease` or `StaggerVars.ease`.
|
|
702
|
+
|
|
703
|
+
| Family | Variants |
|
|
704
|
+
|--------|----------|
|
|
705
|
+
| `linear`, `none` | — |
|
|
706
|
+
| `power1` | `power1.in` · `power1.out` · `power1.inout` |
|
|
707
|
+
| `power2` | `power2.in` · `power2.out` · `power2.inout` |
|
|
708
|
+
| `power3` | `power3.in` · `power3.out` · `power3.inout` |
|
|
709
|
+
| `power4` | `power4.in` · `power4.out` · `power4.inout` |
|
|
710
|
+
| `sine` | `sine.in` · `sine.out` · `sine.inout` |
|
|
711
|
+
| `expo` | `expo.in` · `expo.out` · `expo.inout` |
|
|
712
|
+
| `circ` | `circ.in` · `circ.out` · `circ.inout` |
|
|
713
|
+
| `back` | `back.in` · `back.out` · `back.inout` |
|
|
714
|
+
| `elastic` | `elastic.in` · `elastic.out` · `elastic.inout` |
|
|
715
|
+
| `bounce` | `bounce.in` · `bounce.out` · `bounce.inout` |
|
|
716
|
+
|
|
717
|
+
Unknown strings fall back to `power1.out`.
|
|
718
|
+
|
|
719
|
+
```ts
|
|
720
|
+
Motion('spring-in', '.box', {
|
|
721
|
+
from: { scale: 0 },
|
|
722
|
+
to: { scale: 1 },
|
|
723
|
+
duration: 0.8,
|
|
724
|
+
ease: 'elastic.out',
|
|
725
|
+
}).play();
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
---
|
|
729
|
+
|
|
730
|
+
## Types
|
|
731
|
+
|
|
732
|
+
All types are re-exported from the package entry point.
|
|
733
|
+
|
|
734
|
+
```ts
|
|
735
|
+
import type {
|
|
736
|
+
// Core
|
|
737
|
+
AnimationVars,
|
|
738
|
+
AnimationConfig,
|
|
739
|
+
AnimationEntry,
|
|
740
|
+
TargetInput,
|
|
741
|
+
ObjectTarget,
|
|
742
|
+
AnimationTarget,
|
|
743
|
+
EasingFunction,
|
|
744
|
+
|
|
745
|
+
// Animation options
|
|
746
|
+
StaggerVars,
|
|
747
|
+
RepeatConfig,
|
|
748
|
+
SplitType,
|
|
749
|
+
PathConfig,
|
|
750
|
+
|
|
751
|
+
// Triggers
|
|
752
|
+
HoverConfig,
|
|
753
|
+
ClickConfig,
|
|
754
|
+
ScrollConfig,
|
|
755
|
+
MouseMoveConfig,
|
|
756
|
+
MarkerConfig,
|
|
757
|
+
|
|
758
|
+
// Gesture
|
|
759
|
+
GestureConfig,
|
|
760
|
+
GestureEvent,
|
|
761
|
+
GestureAction,
|
|
762
|
+
GestureInputType,
|
|
763
|
+
|
|
764
|
+
// Cursor
|
|
765
|
+
CursorConfig,
|
|
766
|
+
CursorStateVars,
|
|
767
|
+
CursorSqueezeConfig,
|
|
768
|
+
} from '@motion/sdk';
|
|
769
|
+
|
|
770
|
+
// Namespace import
|
|
771
|
+
import { Types } from '@motion/sdk';
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
---
|
|
775
|
+
|
|
776
|
+
## Browser Build
|
|
777
|
+
|
|
778
|
+
A pre-built IIFE bundle is generated at `dist/motion-sdk.browser.js` for direct `<script>` tag usage:
|
|
779
|
+
|
|
780
|
+
```html
|
|
781
|
+
<script src="motion-sdk.browser.js"></script>
|
|
782
|
+
<script>
|
|
783
|
+
// Globals are exposed on window:
|
|
784
|
+
const { Motion, MotionTimeline } = window;
|
|
785
|
+
|
|
786
|
+
Motion('fade-in', '.hero', {
|
|
787
|
+
from: { opacity: 0 },
|
|
788
|
+
to: { opacity: 1 },
|
|
789
|
+
duration: 1,
|
|
790
|
+
}).onPageLoad();
|
|
791
|
+
</script>
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
To regenerate the browser bundle:
|
|
795
|
+
|
|
796
|
+
```bash
|
|
797
|
+
bun run packages/sdk/scripts/build-iife.ts
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
---
|
|
801
|
+
|
|
802
|
+
## Browser Support
|
|
803
|
+
|
|
804
|
+
Modern evergreen browsers:
|
|
805
|
+
|
|
806
|
+
| Browser | Minimum |
|
|
807
|
+
|---------|---------|
|
|
808
|
+
| Chrome | 90+ |
|
|
809
|
+
| Firefox | 90+ |
|
|
810
|
+
| Safari | 15+ |
|
|
811
|
+
| Edge | 90+ |
|
|
812
|
+
|
|
813
|
+
---
|
|
814
|
+
|
|
815
|
+
## License
|
|
816
|
+
|
|
817
|
+
Proprietary — see [LICENSE](../../LICENSE) for details.
|