anim-engine 0.4.0 → 0.5.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 +283 -499
- package/dist/animation/create-animation.d.ts +12 -0
- package/dist/animation/create-animation.js +18 -22
- package/dist/animation/create-single-tween.d.ts +1 -2
- package/dist/animation/runner.d.ts +1 -1
- package/dist/animation/runner.js +22 -22
- package/dist/animation/update.d.ts +1 -1
- package/dist/animation/update.js +4 -4
- package/dist/domain/animation.d.ts +118 -3
- package/dist/domain/color.d.ts +12 -0
- package/dist/domain/easing.d.ts +12 -0
- package/dist/domain/easing.js +8 -0
- package/dist/domain/index.d.ts +2 -2
- package/dist/domain/interpolation.d.ts +128 -5
- package/dist/domain/resolve-value.d.ts +13 -0
- package/dist/domain/resolve-value.js +8 -0
- package/dist/domain/ticker.d.ts +28 -0
- package/dist/domain/timeline.d.ts +47 -2
- package/dist/index.d.ts +1 -1
- package/dist/lerp/create-lerp.d.ts +13 -1
- package/dist/lerp/create-lerp.js +20 -17
- package/dist/smooth-damp/create-smooth-damp.d.ts +15 -1
- package/dist/smooth-damp/create-smooth-damp.js +33 -28
- package/dist/spring/create-spring.d.ts +12 -1
- package/dist/spring/create-spring.js +16 -18
- package/dist/ticker/get-ticker.d.ts +9 -1
- package/dist/ticker/get-ticker.js +9 -1
- package/dist/timeline/create-timeline.d.ts +16 -6
- package/dist/timeline/create-timeline.js +39 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,51 +5,22 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/anim-engine)
|
|
6
6
|
[](https://github.com/LukeCarlThompson/anim-engine/blob/main/packages/anim-engine/LICENSE)
|
|
7
7
|
[](https://github.com/LukeCarlThompson/anim-engine/actions)
|
|
8
|
-
[](https://www.typescriptlang.org/)
|
|
9
8
|
|
|
10
9
|
</div>
|
|
11
10
|
|
|
12
|
-
**Renderer-agnostic animation for JavaScript runtimes.**
|
|
13
|
-
|
|
14
|
-
- [Getting started](#getting-started)
|
|
15
|
-
- [Design](#design)
|
|
16
|
-
- [Key advantages](#key-advantages)
|
|
17
|
-
- [Primitives](#primitives)
|
|
18
|
-
- [Usage](#usage)
|
|
19
|
-
- [createAnimation (tween)](#createanimation)
|
|
20
|
-
- [createAnimation (keyframes)](#keyframes)
|
|
21
|
-
- [Re-playing and sequencing](#re-playing-and-sequencing)
|
|
22
|
-
- [createTimeline](#createtimeline)
|
|
23
|
-
- [Continuous primitives](#continuous-primitives)
|
|
24
|
-
- [createSmoothClamp](#createsmoothclamp)
|
|
25
|
-
- [Color](#color)
|
|
26
|
-
- [Ticker](#ticker)
|
|
27
|
-
- [Easing](#easing)
|
|
28
|
-
- [Dynamic values](#dynamic-values)
|
|
29
|
-
- [Benchmarks](#benchmarks)
|
|
30
|
-
- [Game engine integration](#game-engine-integration)
|
|
31
|
-
- [API Reference](#api-reference)
|
|
32
|
-
- [License](#license)
|
|
33
|
-
|
|
34
|
-
## Getting started
|
|
11
|
+
**Renderer-agnostic animation for JavaScript runtimes.** Pure-numeric, tree-shakeable, zero dependencies.
|
|
35
12
|
|
|
36
13
|
```sh
|
|
37
14
|
npm install anim-engine
|
|
38
15
|
```
|
|
39
16
|
|
|
40
|
-
ESM only. Tree-shakeable — import only what you use.
|
|
41
|
-
|
|
42
17
|
```ts
|
|
43
18
|
import { createAnimation, getTicker } from "anim-engine";
|
|
44
19
|
|
|
45
|
-
// The ticker drives all animations. Start it once or drive it with an external ticker.
|
|
46
20
|
getTicker().start();
|
|
47
21
|
|
|
48
22
|
const anim = createAnimation({
|
|
49
|
-
from: 0,
|
|
50
|
-
to: 100,
|
|
51
|
-
durationMs: 1000,
|
|
52
|
-
ease: "outCubic",
|
|
23
|
+
from: 0, to: 100, durationMs: 1000, ease: "outCubic",
|
|
53
24
|
onUpdate: (value) => (sprite.x = value),
|
|
54
25
|
});
|
|
55
26
|
|
|
@@ -58,534 +29,403 @@ await anim.play();
|
|
|
58
29
|
|
|
59
30
|
## Design
|
|
60
31
|
|
|
61
|
-
Anim Engine is built around
|
|
32
|
+
Anim Engine is built around the same concepts used in animation applications, applied to pure numbers.
|
|
62
33
|
|
|
63
|
-
|
|
64
|
-
- **Keyframes** — multi-segment interpolation that describes what a single value does over time, with per-segment easing and millisecond timing.
|
|
65
|
-
- **Timeline** — orchestration of multiple animations running in parallel, sequence, or staggered offset. Composes tweens and keyframes.
|
|
34
|
+
**Timed motion** — a fixed path evaluated when `play()` is called.
|
|
66
35
|
|
|
67
|
-
|
|
36
|
+
- **Tween** — animate from value A to value B over a duration with easing.
|
|
37
|
+
- **Keyframes** — multi-segment motion describing a value at specific points in time.
|
|
38
|
+
- **Timeline** — orchestrate multiple keyframe animations in parallel, sequence, or staggered offset.
|
|
68
39
|
|
|
69
|
-
|
|
40
|
+
**Continuous motion** — chases a live target every frame.
|
|
70
41
|
|
|
71
|
-
|
|
42
|
+
- **Spring** — mass-spring-damper physics for bouncy or elastic movement.
|
|
43
|
+
- **Smooth damp** — natural deceleration toward a target (Unity-style).
|
|
44
|
+
- **Lerp** — exponential approach for smooth asymptotic tracking.
|
|
45
|
+
- **Smooth clamp** — asymptotic saturation for capping velocity, torque, or force.
|
|
72
46
|
|
|
73
|
-
|
|
47
|
+
**Color helpers** — Oklab-based composable interpolation.
|
|
74
48
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
| ⚡ **Fast** | 1.7–7.5× faster than GSAP overall; 3.0–6.2× when comparing equivalent `onUpdate` callback dispatch (see [benchmarks](#benchmarks)). |
|
|
78
|
-
| 🪶 **Lightweight** | Tree-shakeable ESM — import only what you use. No DOM, no canvas, no dependencies. |
|
|
79
|
-
| 🎯 **Numbers only** | A single numeric type for all values. No strings, no transforms, no branches. Predictable performance. |
|
|
80
|
-
| 🔌 **Renderer-agnostic** | Feed values to PixiJS, ThreeJS, DOM, canvas2d, or WebGL. Same API everywhere. |
|
|
81
|
-
| 🧩 **Composable models** | Animations, keyframes, and timelines compose cleanly — put tweens inside timelines, nest keyframes anywhere. |
|
|
82
|
-
| 🔄 **Continuous primitives** | Spring, smooth damp, and lerp chase live targets with zero setup — pass `() => value` for dynamic targets. |
|
|
83
|
-
| 🚫 **No GC pressure** | Zero object allocations in hot update paths. State is mutated in place. |
|
|
84
|
-
| 🎨 **Perceptual color** | Oklab interpolation via `lerpRgba` — no muddy browns. Compose it into any `onUpdate`. |
|
|
85
|
-
| 📐 **TypeScript-first** | Full type exports, exhaustive discriminated unions, no `any`. |
|
|
86
|
-
| 🔧 **Ticker control** | Bring your own game loop or use the built-in rAF ticker. Explicit — never auto-starts. |
|
|
49
|
+
- **`lerpRgba`** — interpolates between two RGBA colors in perceptually uniform space.
|
|
50
|
+
- **`hexToRgba`** — parse hex color strings (`#RGB`, `#RRGGBB`, etc.) to normalized RGBA.
|
|
87
51
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
### Timed (return `Animation`)
|
|
52
|
+
### Deliberately decoupled
|
|
91
53
|
|
|
92
|
-
|
|
93
|
-
| ------------------------------------------- | ----------- | ---------------------------------------------------- |
|
|
94
|
-
| [`createAnimation`](#createanimation) | `Animation` | Timed tween from A to B with easing and delay |
|
|
95
|
-
| [`createAnimation` (keyframes)](#keyframes) | `Animation` | Multi-segment interpolation with per-segment easing |
|
|
96
|
-
| [`createTimeline`](#createtimeline) | `Timeline` | Orchestrate multiple animations on a shared timeline |
|
|
54
|
+
Anim Engine works strictly with numbers to stay renderer-agnostic. Each function accepts an `onUpdate` callback with the latest value and velocity — assign it to a DOM element, WebGL object, or any other target. Read-only `value` and `velocity` properties are also exposed for polling inside a game loop.
|
|
97
55
|
|
|
98
|
-
|
|
56
|
+
- [Primitives](#primitives)
|
|
57
|
+
- [At a glance](#at-a-glance)
|
|
58
|
+
- [Examples](#examples)
|
|
59
|
+
- [Easing](#easing)
|
|
60
|
+
- [Dynamic values](#dynamic-values)
|
|
61
|
+
- [Color](#color)
|
|
62
|
+
- [Ticker](#ticker)
|
|
63
|
+
- [Game engine integration](#game-engine-integration)
|
|
64
|
+
- [Benchmarks](#benchmarks)
|
|
99
65
|
|
|
100
|
-
|
|
66
|
+
---
|
|
101
67
|
|
|
102
|
-
|
|
103
|
-
| --------------------------------------- | --------------- | ---------------------------------------------------- |
|
|
104
|
-
| [`createSpring`](#createspring) | `Interpolation` | Physics-based spring (Verlet integration) |
|
|
105
|
-
| [`createSmoothDamp`](#createsmoothdamp) | `Interpolation` | Unity-style smooth damp, parameter-free chase |
|
|
106
|
-
| [`createLerp`](#createlerp) | `Interpolation` | First-order exponential chase, single rate parameter |
|
|
68
|
+
## Primitives
|
|
107
69
|
|
|
108
|
-
|
|
70
|
+
Two families with complementary shapes:
|
|
109
71
|
|
|
110
|
-
|
|
72
|
+
| Family | Functions | Lifespan |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| **Timed** | `createAnimation`, `createTimeline` | Fixed motion path, runs once per `play()` |
|
|
75
|
+
| **Continuous** | `createSpring`, `createSmoothDamp`, `createLerp` | Chases a live target until stopped |
|
|
111
76
|
|
|
112
|
-
|
|
113
|
-
| ----------------------------------------- | -------------------- | --------------------------------------------- |
|
|
114
|
-
| [`createSmoothClamp`](#createsmoothclamp) | `(n: number) => n` | Asymptotic clamp — saturates toward threshold |
|
|
115
|
-
| [`lerpRgba` / `hexToRgba`](#color) | `RgbaTuple` / parser | Perceptually uniform color interpolation |
|
|
77
|
+
All return a control handle with `value`, `velocity`, `status`, and lifecycle methods.
|
|
116
78
|
|
|
117
|
-
|
|
79
|
+
`createSmoothClamp` is a pure function factory (no handle — returns `(input: number) => number`).
|
|
118
80
|
|
|
119
|
-
|
|
81
|
+
## At a glance
|
|
120
82
|
|
|
121
|
-
|
|
83
|
+
### Timed — `Animation`
|
|
122
84
|
|
|
123
85
|
```ts
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
await anim.play(); // plays, resolves when done
|
|
139
|
-
anim.pause();
|
|
140
|
-
anim.resume();
|
|
141
|
-
anim.stop(); // resets to start
|
|
142
|
-
anim.skipToEnd(); // jumps to end, resolves promise
|
|
86
|
+
type Animation = {
|
|
87
|
+
play: () => Promise<void>
|
|
88
|
+
pause: () => void
|
|
89
|
+
resume: () => void
|
|
90
|
+
stop: () => void
|
|
91
|
+
skipToEnd: () => void
|
|
92
|
+
setProgress: (value: number) => void
|
|
93
|
+
|
|
94
|
+
value: number
|
|
95
|
+
velocity: number
|
|
96
|
+
progress: number // 0 → 1
|
|
97
|
+
status: "playing" | "paused" | "stopped"
|
|
98
|
+
durationMs: number
|
|
99
|
+
}
|
|
143
100
|
```
|
|
144
101
|
|
|
145
|
-
**
|
|
146
|
-
|
|
147
|
-
| Option | Type | Default | Description |
|
|
148
|
-
| ------------ | ------------------------------------------- | ------------- | ------------------------------------------------------------ |
|
|
149
|
-
| `from` | `number \| () => number` | — | Start value |
|
|
150
|
-
| `to` | `number \| () => number` | — | End value |
|
|
151
|
-
| `durationMs` | `number \| () => number` | — | Duration in milliseconds |
|
|
152
|
-
| `ease` | `EaseName \| EaseFunction` | `"inOutSine"` | Easing function or name |
|
|
153
|
-
| `onStarted` | `() => void` | — | Called when playback begins |
|
|
154
|
-
| `onUpdate` | `(value: number, velocity: number) => void` | — | Called every frame with current value and velocity (units/s) |
|
|
155
|
-
| `onEnded` | `() => void` | — | Called when animation completes |
|
|
156
|
-
|
|
157
|
-
**Returns:** `Animation`
|
|
158
|
-
|
|
159
|
-
### Keyframes
|
|
160
|
-
|
|
161
|
-
Multi-segment animation with per-keyframe easing. The first keyframe is the starting point (no gap/ease). Each subsequent keyframe specifies the gap from the previous one and an optional easing. Total duration is the sum of all gaps.
|
|
102
|
+
**Options — single tween:**
|
|
162
103
|
|
|
163
104
|
```ts
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
});
|
|
105
|
+
type TweenOptions = {
|
|
106
|
+
from: DynamicValue
|
|
107
|
+
to: DynamicValue
|
|
108
|
+
durationMs: DynamicValue
|
|
109
|
+
ease?: EaseName | EaseFunction
|
|
110
|
+
onStarted?: () => void
|
|
111
|
+
onUpdate?: (value: number, velocity: number) => void
|
|
112
|
+
onProgress?: (progress: number) => void
|
|
113
|
+
onEnded?: () => void
|
|
114
|
+
ticker?: ExternalTicker
|
|
115
|
+
}
|
|
176
116
|
```
|
|
177
117
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
**Keyframe options:**
|
|
181
|
-
|
|
182
|
-
| Option | Type | Description |
|
|
183
|
-
| ------------ | ------------------------------------------- | -------------------------------------------------- |
|
|
184
|
-
| `keyframes` | `Keyframe[]` | Array of `{ value, gap?, ease? }` keyframes |
|
|
185
|
-
| `onStarted` | `() => void` | Called when playback begins |
|
|
186
|
-
| `onUpdate` | `(value: number, velocity: number) => void` | Called every frame with current value and velocity |
|
|
187
|
-
| `onProgress` | `(progress: number) => void` | Called every frame with 0–1 global progress |
|
|
188
|
-
| `onEnded` | `() => void` | Called when the keyframe animation completes |
|
|
189
|
-
|
|
190
|
-
**Returns:** `Animation`
|
|
191
|
-
|
|
192
|
-
### Re-playing and sequencing
|
|
193
|
-
|
|
194
|
-
Since `play()` can be called again after a tween completes, repeat and yoyo patterns are built from the public API rather than baked into the engine:
|
|
118
|
+
**Options — keyframes:**
|
|
195
119
|
|
|
196
120
|
```ts
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
onUpdate: (scale) => sprite.scale.set(scale),
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
// Repeat — just call play() again
|
|
208
|
-
for (let i = 0; i < 3; i++) {
|
|
209
|
-
await anim.play();
|
|
121
|
+
type KeyframeOptions = {
|
|
122
|
+
keyframes: Keyframe[]
|
|
123
|
+
onStarted?: () => void
|
|
124
|
+
onUpdate?: (value: number, velocity: number) => void
|
|
125
|
+
onProgress?: (progress: number) => void
|
|
126
|
+
onEnded?: () => void
|
|
127
|
+
ticker?: ExternalTicker
|
|
210
128
|
}
|
|
211
129
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
to: () => (forward ? 1.3 : 1),
|
|
217
|
-
durationMs: 600,
|
|
218
|
-
ease: "inOutSine",
|
|
219
|
-
onUpdate: (v) => sprite.scale.set(v),
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
for (let i = 0; i < 6; i++) {
|
|
223
|
-
forward = !forward;
|
|
224
|
-
await bounce.play();
|
|
130
|
+
type Keyframe = {
|
|
131
|
+
value: DynamicValue
|
|
132
|
+
gap?: DynamicValue // ms from previous keyframe
|
|
133
|
+
ease?: EaseName | EaseFunction
|
|
225
134
|
}
|
|
226
135
|
```
|
|
227
136
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
```ts
|
|
231
|
-
import { createTimeline } from "anim-engine";
|
|
232
|
-
|
|
233
|
-
const flash = createTimeline([
|
|
234
|
-
{ at: 0, animation: { keyframes: [{ value: 0 }, { value: 1, gap: 300 }] } },
|
|
235
|
-
{ gap: 0, animation: { keyframes: [{ value: 1 }, { value: 0, gap: 300 }] } },
|
|
236
|
-
]);
|
|
237
|
-
await flash.play();
|
|
238
|
-
```
|
|
137
|
+
### Timed — `Timeline`
|
|
239
138
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
Compose multiple keyframe animations on a shared timeline with `at` or `gap` positions. Parallelism comes from multiple layers at the same `at`.
|
|
139
|
+
Layers multiple keyframe animations in parallel or sequence.
|
|
243
140
|
|
|
244
141
|
```ts
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
keyframes: [{ value: -100 }, { value: 0, gap: 800, ease: "outBack" }],
|
|
260
|
-
onUpdate: (v) => (sprite.x = v),
|
|
261
|
-
},
|
|
262
|
-
},
|
|
263
|
-
],
|
|
264
|
-
{
|
|
265
|
-
onProgress: (progress) => console.log(`overall: ${progress}`),
|
|
266
|
-
},
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
timeline.play();
|
|
142
|
+
type Timeline = {
|
|
143
|
+
play: () => Promise<void>
|
|
144
|
+
pause: () => void
|
|
145
|
+
resume: () => void
|
|
146
|
+
stop: () => void
|
|
147
|
+
skipToEnd: () => void
|
|
148
|
+
setProgress: (value: number) => void
|
|
149
|
+
|
|
150
|
+
values: number[] // one per layer
|
|
151
|
+
velocities: number[] // one per layer
|
|
152
|
+
progress: number
|
|
153
|
+
status: "playing" | "paused" | "stopped"
|
|
154
|
+
durationMs: number
|
|
155
|
+
}
|
|
270
156
|
```
|
|
271
157
|
|
|
272
|
-
**Parameters:**
|
|
273
|
-
|
|
274
158
|
```ts
|
|
275
159
|
type TimelineLayer =
|
|
276
|
-
| { animation:
|
|
277
|
-
| { animation:
|
|
160
|
+
| { animation: KeyframeOptions; at: DynamicValue } // absolute position
|
|
161
|
+
| { animation: KeyframeOptions; gap: number } // relative to previous layer end
|
|
278
162
|
```
|
|
279
163
|
|
|
280
|
-
|
|
281
|
-
| -------------------- | -------------------- | -------------------------------------------------------------- |
|
|
282
|
-
| `layers` | `TimelineLayer[]` | Array of layers with `at` (absolute) or `gap` (relative) start |
|
|
283
|
-
| `options.onStarted` | `() => void` | Called when timeline begins |
|
|
284
|
-
| `options.onProgress` | `(progress) => void` | Called every frame with overall 0–1 progress |
|
|
285
|
-
| `options.onEnded` | `() => void` | Called when timeline finishes |
|
|
286
|
-
|
|
287
|
-
`gap` is relative to the end of the previous layer. Use multiple layers at the same `at` for parallel animations. A simple tween is expressed as a 2-keyframe sequence: `{ value: from }, { value: to, gap: durationMs }`.
|
|
288
|
-
|
|
289
|
-
### Continuous primitives
|
|
290
|
-
|
|
291
|
-
Spring, smooth damp, and lerp are **continuous** — they auto-start and chase a target. They return `Interpolation` (no `play`/`pause`/`promise`).
|
|
292
|
-
|
|
293
|
-
#### createSpring
|
|
164
|
+
### Continuous — `Interpolation`
|
|
294
165
|
|
|
295
166
|
```ts
|
|
296
|
-
|
|
167
|
+
type Interpolation = {
|
|
168
|
+
resume: () => void
|
|
169
|
+
stop: () => void
|
|
170
|
+
setValue: (value: number) => void
|
|
171
|
+
|
|
172
|
+
value: number
|
|
173
|
+
velocity: number
|
|
174
|
+
status: "active" | "inactive"
|
|
175
|
+
}
|
|
176
|
+
```
|
|
297
177
|
|
|
298
|
-
|
|
299
|
-
to: () => 100,
|
|
300
|
-
stiffness: 180,
|
|
301
|
-
damping: 12,
|
|
302
|
-
mass: 1,
|
|
303
|
-
precision: 0.01,
|
|
304
|
-
onUpdate: (value, velocity) => {
|
|
305
|
-
sprite.x = value;
|
|
306
|
-
},
|
|
307
|
-
});
|
|
178
|
+
**Options:**
|
|
308
179
|
|
|
309
|
-
|
|
180
|
+
```ts
|
|
181
|
+
type SpringOptions = {
|
|
182
|
+
to: () => number
|
|
183
|
+
stiffness?: DynamicValue // default 180
|
|
184
|
+
damping?: DynamicValue // default 12
|
|
185
|
+
mass?: DynamicValue // default 1
|
|
186
|
+
precision?: number // default 0.01
|
|
187
|
+
onUpdate?: (value: number, velocity: number) => void
|
|
188
|
+
onEnded?: () => void
|
|
189
|
+
ticker?: ExternalTicker
|
|
190
|
+
}
|
|
310
191
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
192
|
+
type SmoothDampOptions = {
|
|
193
|
+
to: () => number
|
|
194
|
+
smoothTimeMs: DynamicValue
|
|
195
|
+
maxSpeed?: DynamicValue
|
|
196
|
+
precision?: number // default 0.01
|
|
197
|
+
onUpdate?: (value: number, velocity: number) => void
|
|
198
|
+
onEnded?: () => void
|
|
199
|
+
ticker?: ExternalTicker
|
|
200
|
+
}
|
|
316
201
|
|
|
317
|
-
|
|
318
|
-
to: () =>
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
onUpdate:
|
|
322
|
-
|
|
202
|
+
type LerpOptions = {
|
|
203
|
+
to: () => number
|
|
204
|
+
smoothTimeMs: DynamicValue
|
|
205
|
+
precision?: number // default 0.01
|
|
206
|
+
onUpdate?: (value: number, velocity: number) => void
|
|
207
|
+
onEnded?: () => void
|
|
208
|
+
ticker?: ExternalTicker
|
|
209
|
+
}
|
|
323
210
|
```
|
|
324
211
|
|
|
325
|
-
|
|
212
|
+
### Common option patterns
|
|
326
213
|
|
|
327
|
-
|
|
214
|
+
| | `from` | `to` / target | `onUpdate` values | `durationMs` / `smoothTimeMs` |
|
|
215
|
+
|---|---|---|---|---|
|
|
216
|
+
| `createAnimation` (tween) | ✅ `DynamicValue` | ✅ `DynamicValue` | single `(value, velocity)` | ✅ `DynamicValue` |
|
|
217
|
+
| `createAnimation` (keyframes) | — (first keyframe) | — (last keyframe) | single `(value, velocity)` | — (sum of gaps) |
|
|
218
|
+
| `createTimeline` | — | — | multi `(values[], velocities[])` | — (computed) |
|
|
219
|
+
| `createSpring` | — (chases current) | ✅ `() => number` | single `(value, velocity)` | — (physics params) |
|
|
220
|
+
| `createSmoothDamp` | — (chases current) | ✅ `() => number` | single `(value, velocity)` | ✅ `DynamicValue` |
|
|
221
|
+
| `createLerp` | — (chases current) | ✅ `() => number` | single `(value, velocity)` | ✅ `DynamicValue` |
|
|
328
222
|
|
|
329
|
-
|
|
223
|
+
### Shared options
|
|
330
224
|
|
|
331
225
|
```ts
|
|
332
|
-
|
|
226
|
+
type DynamicValue = number | (() => number)
|
|
333
227
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
onUpdate: (value, velocity) => (sprite.x = value),
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
damp.setCurrentValue(0); // jump to start — damp chases back to 100
|
|
228
|
+
type ExternalTicker = {
|
|
229
|
+
add: (handler: TickHandler) => void
|
|
230
|
+
remove: (handler: TickHandler) => void
|
|
231
|
+
}
|
|
342
232
|
```
|
|
343
233
|
|
|
344
|
-
|
|
234
|
+
---
|
|
345
235
|
|
|
346
|
-
|
|
236
|
+
## Examples
|
|
347
237
|
|
|
348
|
-
|
|
238
|
+
### Single tween
|
|
349
239
|
|
|
350
240
|
```ts
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
to: () => 100,
|
|
355
|
-
smoothTimeMs: 300, // ms to reach target
|
|
356
|
-
onUpdate: (value, velocity) => (sprite.x = value),
|
|
241
|
+
const anim = createAnimation({
|
|
242
|
+
from: 0, to: 100, durationMs: 2000, ease: "outElastic",
|
|
243
|
+
onUpdate: (v) => (sprite.x = v),
|
|
357
244
|
});
|
|
358
|
-
|
|
359
|
-
lerp.setCurrentValue(0); // jump to start — lerp chases back to 100
|
|
245
|
+
await anim.play();
|
|
360
246
|
```
|
|
361
247
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
**Returns:** `Interpolation`
|
|
365
|
-
|
|
366
|
-
### createSmoothClamp
|
|
248
|
+
### Keyframes
|
|
367
249
|
|
|
368
250
|
```ts
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
251
|
+
createAnimation({
|
|
252
|
+
keyframes: [
|
|
253
|
+
{ value: 0 },
|
|
254
|
+
{ value: 50, gap: 300, ease: "outCubic" },
|
|
255
|
+
{ value: 100, gap: 400 },
|
|
256
|
+
],
|
|
257
|
+
onUpdate: (v) => (sprite.x = v),
|
|
258
|
+
}).play();
|
|
375
259
|
```
|
|
376
260
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
### Color
|
|
380
|
-
|
|
381
|
-
Perceptually uniform Oklab interpolation. Straight RGB lerp produces muddy transitions — red→green goes through brown, blue→yellow goes through grey. Oklab matches human perception: equal steps look like equal changes.
|
|
261
|
+
### Repeat & yoyo
|
|
382
262
|
|
|
383
263
|
```ts
|
|
384
|
-
|
|
264
|
+
const anim = createAnimation({
|
|
265
|
+
from: () => (forward ? 1 : 1.3),
|
|
266
|
+
to: () => (forward ? 1.3 : 1),
|
|
267
|
+
durationMs: 600, ease: "inOutSine",
|
|
268
|
+
onUpdate: (v) => sprite.scale.set(v),
|
|
269
|
+
});
|
|
385
270
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
271
|
+
for (let i = 0; i < 6; i++) {
|
|
272
|
+
forward = !forward;
|
|
273
|
+
await anim.play();
|
|
274
|
+
}
|
|
275
|
+
```
|
|
389
276
|
|
|
390
|
-
|
|
391
|
-
const toColor = hexToRgba("#4ecdc4");
|
|
277
|
+
### Timeline
|
|
392
278
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
sprite.setColor(r, g, b, a);
|
|
401
|
-
},
|
|
279
|
+
```ts
|
|
280
|
+
createTimeline([
|
|
281
|
+
{ at: 0, animation: { keyframes: [{ value: 0 }, { value: 1, gap: 500 }] } },
|
|
282
|
+
{ at: 0, animation: { keyframes: [{ value: -100 }, { value: 0, gap: 800, ease: "outBack" }] } },
|
|
283
|
+
], {
|
|
284
|
+
onProgress: (p) => console.log(p),
|
|
285
|
+
onUpdate: (values, velocities) => { /* one entry per layer */ },
|
|
402
286
|
});
|
|
403
287
|
```
|
|
404
288
|
|
|
405
|
-
|
|
289
|
+
### Spring
|
|
406
290
|
|
|
407
291
|
```ts
|
|
408
|
-
import { createSpring, lerpRgba, hexToRgba } from "anim-engine";
|
|
409
|
-
|
|
410
|
-
const fromColor = hexToRgba("#ff6b6b");
|
|
411
|
-
const toColor = hexToRgba("#4ecdc4");
|
|
412
|
-
|
|
413
292
|
const spring = createSpring({
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
damping: 12,
|
|
418
|
-
onUpdate: (t) => {
|
|
419
|
-
const [r, g, b] = lerpRgba(fromColor, toColor, t);
|
|
420
|
-
sprite.setColor(r, g, b, 1);
|
|
421
|
-
},
|
|
293
|
+
to: () => mouseX,
|
|
294
|
+
stiffness: 180, damping: 12,
|
|
295
|
+
onUpdate: (v) => (sprite.x = v),
|
|
422
296
|
});
|
|
423
297
|
```
|
|
424
298
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
## Ticker
|
|
428
|
-
|
|
429
|
-
The ticker does **not** auto-start. You must explicitly call `start()` (for rAF) or `update(deltaMs)` (for custom game loops) to drive animations.
|
|
430
|
-
|
|
431
|
-
Primitives register themselves with the ticker on creation — no manual registration needed.
|
|
432
|
-
|
|
433
|
-
**Standalone (rAF):** `getTicker().start()` uses its own `requestAnimationFrame` loop. Best for DOM-based demos or when you don't have a game loop.
|
|
299
|
+
### Smooth damp
|
|
434
300
|
|
|
435
301
|
```ts
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
getTicker().start(); // starts the rAF loop — call once
|
|
439
|
-
|
|
440
|
-
createAnimation({
|
|
441
|
-
from: 0,
|
|
442
|
-
to: 100,
|
|
443
|
-
durationMs: 2000,
|
|
444
|
-
ease: "outElastic",
|
|
302
|
+
createSmoothDamp({
|
|
303
|
+
to: () => 100, smoothTimeMs: 300,
|
|
445
304
|
onUpdate: (v) => (sprite.x = v),
|
|
446
|
-
})
|
|
305
|
+
});
|
|
447
306
|
```
|
|
448
307
|
|
|
449
|
-
|
|
308
|
+
### Lerp
|
|
450
309
|
|
|
451
310
|
```ts
|
|
452
|
-
|
|
311
|
+
createLerp({
|
|
312
|
+
to: () => 100, smoothTimeMs: 300,
|
|
313
|
+
onUpdate: (v) => (sprite.x = v),
|
|
314
|
+
});
|
|
315
|
+
```
|
|
453
316
|
|
|
454
|
-
|
|
317
|
+
### Smooth clamp
|
|
455
318
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
// ... physics, rendering
|
|
460
|
-
}
|
|
319
|
+
```ts
|
|
320
|
+
const clamp = createSmoothClamp(45);
|
|
321
|
+
clamp(1000); // ≈ 44.96 (asymptotic to 45)
|
|
461
322
|
```
|
|
462
323
|
|
|
463
|
-
|
|
324
|
+
---
|
|
464
325
|
|
|
465
326
|
## Easing
|
|
466
327
|
|
|
467
|
-
31
|
|
328
|
+
31 named presets plus custom cubic bezier:
|
|
468
329
|
|
|
469
330
|
```ts
|
|
470
|
-
|
|
331
|
+
type EaseName =
|
|
332
|
+
| "linear" | "inQuad" | "outQuad" | "inOutQuad"
|
|
333
|
+
| "inCubic" | "outCubic" | "inOutCubic"
|
|
334
|
+
| "inQuart" | "outQuart" | "inOutQuart"
|
|
335
|
+
| "inQuint" | "outQuint" | "inOutQuint"
|
|
336
|
+
| "inSine" | "outSine" | "inOutSine"
|
|
337
|
+
| "inExpo" | "outExpo" | "inOutExpo"
|
|
338
|
+
| "inCirc" | "outCirc" | "inOutCirc"
|
|
339
|
+
| "inBack" | "outBack" | "inOutBack"
|
|
340
|
+
| "inElastic" | "outElastic" | "inOutElastic"
|
|
341
|
+
| "inBounce" | "outBounce" | "inOutBounce"
|
|
342
|
+
|
|
343
|
+
type EaseFunction = (t: number) => number
|
|
344
|
+
```
|
|
471
345
|
|
|
472
|
-
|
|
346
|
+
Custom bezier:
|
|
473
347
|
|
|
474
|
-
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
to: 100,
|
|
478
|
-
durationMs: 1000,
|
|
479
|
-
ease: customEase,
|
|
480
|
-
onUpdate: (v) => (sprite.x = v),
|
|
481
|
-
});
|
|
348
|
+
```ts
|
|
349
|
+
const ease = cubicBezier(0.25, 0.1, 0.25, 1);
|
|
350
|
+
createAnimation({ from: 0, to: 100, durationMs: 1000, ease, ... });
|
|
482
351
|
```
|
|
483
352
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
**Supported ease names:** `linear`, `inQuad`, `outQuad`, `inOutQuad`, `inCubic`, `outCubic`, `inOutCubic`, `inQuart`, `outQuart`, `inOutQuart`, `inQuint`, `outQuint`, `inOutQuint`, `inSine`, `outSine`, `inOutSine`, `inExpo`, `outExpo`, `inOutExpo`, `inCirc`, `outCirc`, `inOutCirc`, `inBack`, `outBack`, `inOutBack`, `inElastic`, `outElastic`, `inOutElastic`, `inBounce`, `outBounce`, `inOutBounce`.
|
|
353
|
+
---
|
|
487
354
|
|
|
488
355
|
## Dynamic values
|
|
489
356
|
|
|
490
|
-
|
|
357
|
+
`DynamicValue` (`number | (() => number)`) is resolved at different frequencies depending on the primitive:
|
|
491
358
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
In `createAnimation` — tweens and keyframes — all dynamic values are resolved **once when `play()` is called** and cached for the duration of that run. The frame-hot update path reads from the cache with zero overhead:
|
|
359
|
+
- **Timed animations** — resolved once per `play()` call and cached for the run.
|
|
360
|
+
- **Continuous primitives** — resolved every frame (targets are live).
|
|
495
361
|
|
|
496
362
|
```ts
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
durationMs: () => 500 /
|
|
501
|
-
keyframes: [
|
|
502
|
-
|
|
503
|
-
{ at: () => scrollHeight * 0.3, value: 50 },
|
|
504
|
-
{ at: () => scrollHeight, value: 100 },
|
|
505
|
-
],
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
// Values resolved here, cached for duration
|
|
509
|
-
await anim.play();
|
|
363
|
+
// Resolved at play time, cached for duration
|
|
364
|
+
createAnimation({
|
|
365
|
+
from: () => getX(),
|
|
366
|
+
durationMs: () => 500 / speed,
|
|
367
|
+
keyframes: [{ value: 0 }, { value: () => targetY, gap: 300 }],
|
|
368
|
+
}).play();
|
|
510
369
|
|
|
511
|
-
//
|
|
512
|
-
|
|
370
|
+
// Resolved every frame
|
|
371
|
+
createSpring({ to: () => mouseX, ... });
|
|
513
372
|
```
|
|
514
373
|
|
|
515
|
-
|
|
374
|
+
---
|
|
516
375
|
|
|
517
|
-
|
|
376
|
+
## Color
|
|
518
377
|
|
|
519
|
-
|
|
378
|
+
Oklab (perceptually uniform) color interpolation via `lerpRgba`.
|
|
520
379
|
|
|
521
380
|
```ts
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
381
|
+
const from = hexToRgba("#ff6b6b"); // [1, 0.42, 0.42, 1]
|
|
382
|
+
const to = hexToRgba("#4ecdc4");
|
|
383
|
+
|
|
384
|
+
createAnimation({
|
|
385
|
+
from: 0, to: 1, durationMs: 2000,
|
|
386
|
+
onUpdate: (t) => {
|
|
387
|
+
const [r, g, b, a] = lerpRgba(from, to, t);
|
|
388
|
+
sprite.setColor(r, g, b, a);
|
|
389
|
+
},
|
|
526
390
|
});
|
|
527
391
|
```
|
|
528
392
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
## Benchmarks
|
|
393
|
+
Accepts `#RGB`, `#RGBA`, `#RRGGBB`, `#RRGGBBAA` formats.
|
|
532
394
|
|
|
533
|
-
|
|
395
|
+
---
|
|
534
396
|
|
|
535
|
-
|
|
397
|
+
## Ticker
|
|
536
398
|
|
|
537
|
-
|
|
399
|
+
Does **not** auto-start. Explicit control:
|
|
538
400
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
| **Single tween** (cubic, 1000 frames) | 40,814 ops/s | 10,776 ops/s | 3.8× faster |
|
|
542
|
-
| **Single tween** (linear, 1000 frames) | 28,590 ops/s | 14,499 ops/s | 2.0× faster |
|
|
543
|
-
| **Single tween** (cubic bezier, 1000 frames) | 21,449 ops/s | 12,637 ops/s | 1.7× faster |
|
|
544
|
-
| **Keyframe** (3 segments, 1000 frames) | 26,741 ops/s | 3,549 ops/s | 7.5× faster |
|
|
545
|
-
| **50 concurrent tweens** (500 frames) | 892 ops/s | 438 ops/s | 2.0× faster |
|
|
546
|
-
| **200 concurrent tweens** (500 frames) | 200 ops/s | 106 ops/s | 1.9× faster |
|
|
547
|
-
| **1000 concurrent tweens** (500 frames) | 38 ops/s | 22 ops/s | 1.7× faster |
|
|
548
|
-
| **50 concurrent keyframes** (500 frames) | 934 ops/s | 174 ops/s | 5.4× faster |
|
|
549
|
-
| **50-layer timeline** (staggered, 500 frames) | 710 ops/s | 404 ops/s | 1.8× faster |
|
|
550
|
-
| **50 tweens re-play** (2 cycles, 500 frames) | 558 ops/s | 128 ops/s | 4.4× faster |
|
|
401
|
+
```ts
|
|
402
|
+
import { getTicker } from "anim-engine";
|
|
551
403
|
|
|
552
|
-
|
|
404
|
+
// rAF mode
|
|
405
|
+
getTicker().start();
|
|
553
406
|
|
|
554
|
-
|
|
407
|
+
// Custom game loop
|
|
408
|
+
const ticker = getTicker();
|
|
409
|
+
function loop(dt: number) {
|
|
410
|
+
ticker.update(dt);
|
|
411
|
+
}
|
|
412
|
+
```
|
|
555
413
|
|
|
556
|
-
|
|
557
|
-
| -------------------------------------- | ------------ | --------------- | ----------- |
|
|
558
|
-
| **Single tween** (cubic, 1000 frames) | 40,814 ops/s | 6,618 ops/s | 6.2× faster |
|
|
559
|
-
| **50 concurrent tweens** (500 frames) | 892 ops/s | 273 ops/s | 3.3× faster |
|
|
560
|
-
| **200 concurrent tweens** (500 frames) | 200 ops/s | 66 ops/s | 3.0× faster |
|
|
414
|
+
Stop with `getTicker().stop()`.
|
|
561
415
|
|
|
562
|
-
|
|
416
|
+
---
|
|
563
417
|
|
|
564
418
|
## Game engine integration
|
|
565
419
|
|
|
566
420
|
### PixiJS
|
|
567
421
|
|
|
568
422
|
```ts
|
|
569
|
-
import { Application, Sprite } from "pixi.js";
|
|
570
423
|
import { createAnimation, getTicker } from "anim-engine";
|
|
424
|
+
const ticker = getTicker();
|
|
571
425
|
|
|
572
|
-
|
|
573
|
-
await app.init({ width: 800, height: 600 });
|
|
574
|
-
|
|
575
|
-
const sprite = Sprite.from("texture.png");
|
|
576
|
-
app.stage.addChild(sprite);
|
|
426
|
+
app.ticker.add((delta) => ticker.update(delta.deltaMS));
|
|
577
427
|
|
|
578
|
-
|
|
579
|
-
const animEngineTicker = getTicker();
|
|
580
|
-
app.ticker.add((delta) => {
|
|
581
|
-
animEngineTicker.update(delta.deltaMS);
|
|
582
|
-
});
|
|
583
|
-
|
|
584
|
-
createAnimation({
|
|
585
|
-
from: 0,
|
|
586
|
-
to: 300,
|
|
587
|
-
durationMs: 2000,
|
|
588
|
-
ease: "outElastic",
|
|
428
|
+
createAnimation({ from: 0, to: 300, durationMs: 2000, ease: "outElastic",
|
|
589
429
|
onUpdate: (x) => (sprite.x = x),
|
|
590
430
|
}).play();
|
|
591
431
|
```
|
|
@@ -593,95 +433,39 @@ createAnimation({
|
|
|
593
433
|
### ThreeJS
|
|
594
434
|
|
|
595
435
|
```ts
|
|
596
|
-
import * as THREE from "three";
|
|
597
|
-
import { createAnimation, getTicker, createSmoothDamp } from "anim-engine";
|
|
598
|
-
|
|
599
|
-
const scene = new THREE.Scene();
|
|
600
|
-
const camera = new THREE.PerspectiveCamera(75, innerWidth / innerHeight, 0.1, 1000);
|
|
601
|
-
const renderer = new THREE.WebGLRenderer();
|
|
602
|
-
|
|
603
|
-
const mesh = new THREE.Mesh(
|
|
604
|
-
new THREE.BoxGeometry(),
|
|
605
|
-
new THREE.MeshStandardMaterial({ color: "#667eea" }),
|
|
606
|
-
);
|
|
607
|
-
scene.add(mesh);
|
|
608
|
-
|
|
609
|
-
const animEngineTicker = getTicker();
|
|
610
436
|
const clock = new THREE.Clock();
|
|
437
|
+
const ticker = getTicker();
|
|
611
438
|
|
|
612
439
|
function animate() {
|
|
613
440
|
requestAnimationFrame(animate);
|
|
614
|
-
|
|
441
|
+
ticker.update(clock.getDelta() * 1000);
|
|
615
442
|
renderer.render(scene, camera);
|
|
616
443
|
}
|
|
617
444
|
animate();
|
|
618
|
-
|
|
619
|
-
createAnimation({
|
|
620
|
-
from: 0,
|
|
621
|
-
to: Math.PI * 2,
|
|
622
|
-
durationMs: 3000,
|
|
623
|
-
ease: "inOutCubic",
|
|
624
|
-
onUpdate: (angle) => (mesh.rotation.y = angle),
|
|
625
|
-
}).play();
|
|
626
445
|
```
|
|
627
446
|
|
|
628
|
-
|
|
447
|
+
---
|
|
629
448
|
|
|
630
|
-
|
|
631
|
-
import { getTicker, createSpring } from "anim-engine";
|
|
449
|
+
## Benchmarks
|
|
632
450
|
|
|
633
|
-
|
|
451
|
+
vs GSAP (vitest bench, Apple Silicon M-series, Node 24). Matched easing functions.
|
|
634
452
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
453
|
+
| Benchmark | anim-engine | GSAP | Ratio |
|
|
454
|
+
|---|---|---|---|
|
|
455
|
+
| Single tween (cubic, 1000 frames) | 40,814 ops/s | 10,776 ops/s | 3.8× |
|
|
456
|
+
| Single tween (bezier, 1000 frames) | 21,449 ops/s | 12,637 ops/s | 1.7× |
|
|
457
|
+
| Keyframe (3 segments, 1000 frames) | 26,741 ops/s | 3,549 ops/s | 7.5× |
|
|
458
|
+
| 50 concurrent tweens (500 frames) | 892 ops/s | 438 ops/s | 2.0× |
|
|
459
|
+
| 200 concurrent tweens (500 frames) | 200 ops/s | 106 ops/s | 1.9× |
|
|
460
|
+
| 1000 concurrent tweens (500 frames) | 38 ops/s | 22 ops/s | 1.7× |
|
|
461
|
+
| 50-layer timeline (500 frames) | 710 ops/s | 404 ops/s | 1.8× |
|
|
462
|
+
| 50 tweens re-play (500 frames) | 558 ops/s | 128 ops/s | 4.4× |
|
|
638
463
|
|
|
639
|
-
|
|
640
|
-
// ... physics, rendering, etc.
|
|
464
|
+
vs GSAP `onUpdate` (apples-to-apples): **3.0–6.2× faster**.
|
|
641
465
|
|
|
642
|
-
|
|
643
|
-
}
|
|
644
|
-
requestAnimationFrame(gameLoop);
|
|
645
|
-
```
|
|
466
|
+
Run locally: `npm run bench`
|
|
646
467
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
### Functions
|
|
650
|
-
|
|
651
|
-
| Export | Description |
|
|
652
|
-
| ---------------------------------- | ----------------------------------- |
|
|
653
|
-
| `createAnimation(options)` | Timed or keyframe animation |
|
|
654
|
-
| `createTimeline(layers, options?)` | Composited timeline of animations |
|
|
655
|
-
| `createSpring(options)` | Physics spring (Verlet integration) |
|
|
656
|
-
| `createSmoothDamp(options)` | Unity-style smooth damp |
|
|
657
|
-
| `createLerp(options)` | Exponential lerp chase |
|
|
658
|
-
| `createSmoothClamp(threshold)` | Asymptotic clamp factory |
|
|
659
|
-
| `getTicker()` | Singleton ticker |
|
|
660
|
-
| `cubicBezier(p1x, p1y, p2x, p2y)` | Custom cubic bezier easing |
|
|
661
|
-
| `lerpRgba(from, to, t)` | Oklab color interpolation |
|
|
662
|
-
| `hexToRgba(hex)` | Parse hex color to normalized RGBA |
|
|
663
|
-
|
|
664
|
-
### Type exports
|
|
665
|
-
|
|
666
|
-
| Type | Description |
|
|
667
|
-
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
668
|
-
| `Animation` | `play`, `pause`, `resume`, `stop`, `skipToEnd`, `kill`, `setProgress`, `currentValue`, `velocity`, `progress`, `status` |
|
|
669
|
-
| `Interpolation` | `start`, `stop`, `kill`, `setCurrentValue`, `currentValue`, `velocity`, `status` |
|
|
670
|
-
| `Timeline` | `play`, `pause`, `resume`, `stop`, `skipToEnd`, `kill`, `setProgress`, `progress`, `status` |
|
|
671
|
-
| `EaseName` | Union of 31 ease name strings |
|
|
672
|
-
| `EaseFunction` | `(t: number) => number` |
|
|
673
|
-
| `DynamicValue` | `number \| (() => number)` |
|
|
674
|
-
| `AnimationStatus` | `"playing" \| "paused" \| "stopped" \| "dead"` (for `Animation` / `Timeline`) |
|
|
675
|
-
| `InterpolationStatus` | `"active" \| "inactive" \| "dead"` (for `Interpolation`) |
|
|
676
|
-
| `AnimationOptions` | Single tween or keyframe animation options (discriminated union) |
|
|
677
|
-
| `Keyframe` | `{ value, gap?, ease? }` |
|
|
678
|
-
| `KeyframeAnimationOptions` | `{ keyframes: Keyframe[], onStarted?, onUpdate?, onProgress?, onEnded? }` |
|
|
679
|
-
| `TimelineLayer` | `{ animation: KeyframeAnimationOptions; at: DynamicValue } \| { animation: KeyframeAnimationOptions; gap: number }` |
|
|
680
|
-
| `SpringOptions` | `to`, `stiffness`, `damping`, `mass`, `precision?`, `onUpdate`, `onEnded` |
|
|
681
|
-
| `SmoothDampOptions` | `to`, `smoothTimeMs`, `maxSpeed?`, `precision?`, `onUpdate`, `onEnded` |
|
|
682
|
-
| `LerpOptions` | `to`, `smoothTimeMs`, `precision?`, `onUpdate`, `onEnded` |
|
|
683
|
-
| `RgbaTuple` | `readonly [number, number, number, number]` |
|
|
684
|
-
| `Ticker` | `start`, `stop`, `update`, `add`, `remove` |
|
|
468
|
+
---
|
|
685
469
|
|
|
686
470
|
## License
|
|
687
471
|
|