anim-engine 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +300 -498
- package/SKILL.md +515 -0
- 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 +46 -10
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -5,587 +5,445 @@
|
|
|
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
|
|
|
56
27
|
await anim.play();
|
|
57
28
|
```
|
|
58
29
|
|
|
59
|
-
|
|
30
|
+
> AI agents: this package includes a [`SKILL.md`](SKILL.md) with in-depth usage guidance for agent-assisted development.
|
|
60
31
|
|
|
61
|
-
|
|
32
|
+
## Design
|
|
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
|
+
Anim Engine is built around the same concepts used in animation applications, applied to pure numbers.
|
|
66
35
|
|
|
67
|
-
|
|
36
|
+
**Timed motion** — a fixed path evaluated when `play()` is called.
|
|
68
37
|
|
|
69
|
-
|
|
38
|
+
- **Tween** — animate from value A to value B over a duration with easing.
|
|
39
|
+
- **Keyframes** — multi-segment motion describing a value at specific points in time.
|
|
40
|
+
- **Timeline** — orchestrate multiple keyframe animations in parallel, sequence, or staggered offset.
|
|
70
41
|
|
|
71
|
-
|
|
42
|
+
**Continuous motion** — chases a live target every frame.
|
|
72
43
|
|
|
73
|
-
|
|
44
|
+
- **Spring** — mass-spring-damper physics for bouncy or elastic movement.
|
|
45
|
+
- **Smooth damp** — natural deceleration toward a target (Unity-style).
|
|
46
|
+
- **Lerp** — exponential approach for smooth asymptotic tracking.
|
|
47
|
+
- **Smooth clamp** — asymptotic saturation for capping velocity, torque, or force.
|
|
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
|
+
**Color helpers** — Oklab-based composable interpolation.
|
|
87
50
|
|
|
88
|
-
|
|
51
|
+
- **`lerpRgba`** — interpolates between two RGBA colors in perceptually uniform space.
|
|
52
|
+
- **`hexToRgba`** — parse hex color strings (`#RGB`, `#RRGGBB`, etc.) to normalized RGBA.
|
|
89
53
|
|
|
90
|
-
###
|
|
54
|
+
### Deliberately decoupled
|
|
91
55
|
|
|
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 |
|
|
56
|
+
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
57
|
|
|
98
|
-
|
|
58
|
+
- [Primitives](#primitives)
|
|
59
|
+
- [At a glance](#at-a-glance)
|
|
60
|
+
- [Examples](#examples)
|
|
61
|
+
- [Easing](#easing)
|
|
62
|
+
- [Dynamic values](#dynamic-values)
|
|
63
|
+
- [Color](#color)
|
|
64
|
+
- [Ticker](#ticker)
|
|
65
|
+
- [Game engine integration](#game-engine-integration)
|
|
66
|
+
- [Benchmarks](#benchmarks)
|
|
99
67
|
|
|
100
|
-
|
|
68
|
+
---
|
|
101
69
|
|
|
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 |
|
|
70
|
+
## Primitives
|
|
107
71
|
|
|
108
|
-
|
|
72
|
+
Two families with complementary shapes:
|
|
109
73
|
|
|
110
|
-
|
|
74
|
+
| Family | Functions | Lifespan |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| **Timed** | `createAnimation`, `createTimeline` | Fixed motion path, runs once per `play()` |
|
|
77
|
+
| **Continuous** | `createSpring`, `createSmoothDamp`, `createLerp` | Chases a live target until stopped |
|
|
111
78
|
|
|
112
|
-
|
|
113
|
-
| ----------------------------------------- | -------------------- | --------------------------------------------- |
|
|
114
|
-
| [`createSmoothClamp`](#createsmoothclamp) | `(n: number) => n` | Asymptotic clamp — saturates toward threshold |
|
|
115
|
-
| [`lerpRgba` / `hexToRgba`](#color) | `RgbaTuple` / parser | Perceptually uniform color interpolation |
|
|
79
|
+
All return a control handle with `value`, `velocity`, `status`, and lifecycle methods.
|
|
116
80
|
|
|
117
|
-
|
|
81
|
+
`createSmoothClamp` is a pure function factory (no handle — returns `(input: number) => number`).
|
|
118
82
|
|
|
119
|
-
|
|
83
|
+
## At a glance
|
|
120
84
|
|
|
121
|
-
|
|
85
|
+
### Timed — `Animation`
|
|
122
86
|
|
|
123
87
|
```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
|
|
88
|
+
type Animation = {
|
|
89
|
+
play: () => Promise<void>
|
|
90
|
+
pause: () => void
|
|
91
|
+
resume: () => void
|
|
92
|
+
stop: () => void
|
|
93
|
+
skipToEnd: () => void
|
|
94
|
+
setProgress: (value: number) => void
|
|
95
|
+
|
|
96
|
+
value: number
|
|
97
|
+
velocity: number
|
|
98
|
+
progress: number // 0 → 1
|
|
99
|
+
status: "playing" | "paused" | "stopped"
|
|
100
|
+
durationMs: number
|
|
101
|
+
}
|
|
143
102
|
```
|
|
144
103
|
|
|
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.
|
|
104
|
+
**Options — single tween:**
|
|
162
105
|
|
|
163
106
|
```ts
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
});
|
|
107
|
+
type TweenOptions = {
|
|
108
|
+
from: DynamicValue
|
|
109
|
+
to: DynamicValue
|
|
110
|
+
durationMs: DynamicValue
|
|
111
|
+
ease?: EaseName | EaseFunction
|
|
112
|
+
onStarted?: () => void
|
|
113
|
+
onUpdate?: (value: number, velocity: number) => void
|
|
114
|
+
onProgress?: (progress: number) => void
|
|
115
|
+
onEnded?: () => void
|
|
116
|
+
ticker?: ExternalTicker
|
|
117
|
+
}
|
|
176
118
|
```
|
|
177
119
|
|
|
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:
|
|
120
|
+
**Options — keyframes:**
|
|
195
121
|
|
|
196
122
|
```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();
|
|
123
|
+
type KeyframeOptions = {
|
|
124
|
+
keyframes: Keyframe[]
|
|
125
|
+
onStarted?: () => void
|
|
126
|
+
onUpdate?: (value: number, velocity: number) => void
|
|
127
|
+
onProgress?: (progress: number) => void
|
|
128
|
+
onEnded?: () => void
|
|
129
|
+
ticker?: ExternalTicker
|
|
210
130
|
}
|
|
211
131
|
|
|
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();
|
|
132
|
+
type Keyframe = {
|
|
133
|
+
value: DynamicValue
|
|
134
|
+
gap?: DynamicValue // ms from previous keyframe
|
|
135
|
+
ease?: EaseName | EaseFunction
|
|
225
136
|
}
|
|
226
137
|
```
|
|
227
138
|
|
|
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
|
-
```
|
|
239
|
-
|
|
240
|
-
### createTimeline
|
|
139
|
+
### Timed — `Timeline`
|
|
241
140
|
|
|
242
|
-
|
|
141
|
+
Layers multiple keyframe animations in parallel or sequence.
|
|
243
142
|
|
|
244
143
|
```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();
|
|
144
|
+
type Timeline = {
|
|
145
|
+
play: () => Promise<void>
|
|
146
|
+
pause: () => void
|
|
147
|
+
resume: () => void
|
|
148
|
+
stop: () => void
|
|
149
|
+
skipToEnd: () => void
|
|
150
|
+
setProgress: (value: number) => void
|
|
151
|
+
|
|
152
|
+
values: number[] // one per layer
|
|
153
|
+
velocities: number[] // one per layer
|
|
154
|
+
progress: number
|
|
155
|
+
status: "playing" | "paused" | "stopped"
|
|
156
|
+
durationMs: number
|
|
157
|
+
}
|
|
270
158
|
```
|
|
271
159
|
|
|
272
|
-
**Parameters:**
|
|
273
|
-
|
|
274
160
|
```ts
|
|
275
161
|
type TimelineLayer =
|
|
276
|
-
| { animation:
|
|
277
|
-
| { animation:
|
|
162
|
+
| { animation: KeyframeOptions; at: DynamicValue } // absolute position
|
|
163
|
+
| { animation: KeyframeOptions; gap: number } // relative to previous layer end
|
|
278
164
|
```
|
|
279
165
|
|
|
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
|
|
166
|
+
### Continuous — `Interpolation`
|
|
294
167
|
|
|
295
168
|
```ts
|
|
296
|
-
|
|
169
|
+
type Interpolation = {
|
|
170
|
+
resume: () => void
|
|
171
|
+
stop: () => void
|
|
172
|
+
setValue: (value: number) => void
|
|
173
|
+
|
|
174
|
+
value: number
|
|
175
|
+
velocity: number
|
|
176
|
+
status: "active" | "inactive"
|
|
177
|
+
}
|
|
178
|
+
```
|
|
297
179
|
|
|
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
|
-
});
|
|
180
|
+
**Options:**
|
|
308
181
|
|
|
309
|
-
|
|
182
|
+
```ts
|
|
183
|
+
type SpringOptions = {
|
|
184
|
+
to: () => number
|
|
185
|
+
stiffness?: DynamicValue // default 180
|
|
186
|
+
damping?: DynamicValue // default 12
|
|
187
|
+
mass?: DynamicValue // default 1
|
|
188
|
+
precision?: number // default 0.01
|
|
189
|
+
onUpdate?: (value: number, velocity: number) => void
|
|
190
|
+
onEnded?: () => void
|
|
191
|
+
ticker?: ExternalTicker
|
|
192
|
+
}
|
|
310
193
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
194
|
+
type SmoothDampOptions = {
|
|
195
|
+
to: () => number
|
|
196
|
+
smoothTimeMs: DynamicValue
|
|
197
|
+
maxSpeed?: DynamicValue
|
|
198
|
+
precision?: number // default 0.01
|
|
199
|
+
onUpdate?: (value: number, velocity: number) => void
|
|
200
|
+
onEnded?: () => void
|
|
201
|
+
ticker?: ExternalTicker
|
|
202
|
+
}
|
|
316
203
|
|
|
317
|
-
|
|
318
|
-
to: () =>
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
onUpdate:
|
|
322
|
-
|
|
204
|
+
type LerpOptions = {
|
|
205
|
+
to: () => number
|
|
206
|
+
smoothTimeMs: DynamicValue
|
|
207
|
+
precision?: number // default 0.01
|
|
208
|
+
onUpdate?: (value: number, velocity: number) => void
|
|
209
|
+
onEnded?: () => void
|
|
210
|
+
ticker?: ExternalTicker
|
|
211
|
+
}
|
|
323
212
|
```
|
|
324
213
|
|
|
325
|
-
|
|
214
|
+
### Common option patterns
|
|
326
215
|
|
|
327
|
-
|
|
216
|
+
| | `from` | `to` / target | `onUpdate` values | `durationMs` / `smoothTimeMs` |
|
|
217
|
+
|---|---|---|---|---|
|
|
218
|
+
| `createAnimation` (tween) | ✅ `DynamicValue` | ✅ `DynamicValue` | single `(value, velocity)` | ✅ `DynamicValue` |
|
|
219
|
+
| `createAnimation` (keyframes) | — (first keyframe) | — (last keyframe) | single `(value, velocity)` | — (sum of gaps) |
|
|
220
|
+
| `createTimeline` | — | — | multi `(values[], velocities[])` | — (computed) |
|
|
221
|
+
| `createSpring` | — (chases current) | ✅ `() => number` | single `(value, velocity)` | — (physics params) |
|
|
222
|
+
| `createSmoothDamp` | — (chases current) | ✅ `() => number` | single `(value, velocity)` | ✅ `DynamicValue` |
|
|
223
|
+
| `createLerp` | — (chases current) | ✅ `() => number` | single `(value, velocity)` | ✅ `DynamicValue` |
|
|
328
224
|
|
|
329
|
-
|
|
225
|
+
### Shared options
|
|
330
226
|
|
|
331
227
|
```ts
|
|
332
|
-
|
|
228
|
+
type DynamicValue = number | (() => number)
|
|
333
229
|
|
|
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
|
|
230
|
+
type ExternalTicker = {
|
|
231
|
+
add: (handler: TickHandler) => void
|
|
232
|
+
remove: (handler: TickHandler) => void
|
|
233
|
+
}
|
|
342
234
|
```
|
|
343
235
|
|
|
344
|
-
|
|
236
|
+
---
|
|
345
237
|
|
|
346
|
-
|
|
238
|
+
## Examples
|
|
347
239
|
|
|
348
|
-
|
|
240
|
+
### Single tween
|
|
349
241
|
|
|
350
242
|
```ts
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
to: () => 100,
|
|
355
|
-
smoothTimeMs: 300, // ms to reach target
|
|
356
|
-
onUpdate: (value, velocity) => (sprite.x = value),
|
|
243
|
+
const anim = createAnimation({
|
|
244
|
+
from: 0, to: 100, durationMs: 2000, ease: "outElastic",
|
|
245
|
+
onUpdate: (v) => (sprite.x = v),
|
|
357
246
|
});
|
|
358
|
-
|
|
359
|
-
lerp.setCurrentValue(0); // jump to start — lerp chases back to 100
|
|
247
|
+
await anim.play();
|
|
360
248
|
```
|
|
361
249
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
**Returns:** `Interpolation`
|
|
365
|
-
|
|
366
|
-
### createSmoothClamp
|
|
250
|
+
### Keyframes
|
|
367
251
|
|
|
368
252
|
```ts
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
253
|
+
createAnimation({
|
|
254
|
+
keyframes: [
|
|
255
|
+
{ value: 0 },
|
|
256
|
+
{ value: 50, gap: 300, ease: "outCubic" },
|
|
257
|
+
{ value: 100, gap: 400 },
|
|
258
|
+
],
|
|
259
|
+
onUpdate: (v) => (sprite.x = v),
|
|
260
|
+
}).play();
|
|
375
261
|
```
|
|
376
262
|
|
|
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.
|
|
263
|
+
### Repeat & yoyo
|
|
382
264
|
|
|
383
265
|
```ts
|
|
384
|
-
|
|
266
|
+
const anim = createAnimation({
|
|
267
|
+
from: () => (forward ? 1 : 1.3),
|
|
268
|
+
to: () => (forward ? 1.3 : 1),
|
|
269
|
+
durationMs: 600, ease: "inOutSine",
|
|
270
|
+
onUpdate: (v) => sprite.scale.set(v),
|
|
271
|
+
});
|
|
385
272
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
273
|
+
for (let i = 0; i < 6; i++) {
|
|
274
|
+
forward = !forward;
|
|
275
|
+
await anim.play();
|
|
276
|
+
}
|
|
277
|
+
```
|
|
389
278
|
|
|
390
|
-
|
|
391
|
-
const toColor = hexToRgba("#4ecdc4");
|
|
279
|
+
### Timeline
|
|
392
280
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
281
|
+
```ts
|
|
282
|
+
createTimeline([
|
|
283
|
+
{
|
|
284
|
+
at: 0,
|
|
285
|
+
animation: {
|
|
286
|
+
keyframes: [{ value: 0 }, { value: 1, gap: 500 }],
|
|
287
|
+
onEnded: () => console.log("layer 1 done"),
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
at: 0,
|
|
292
|
+
animation: {
|
|
293
|
+
keyframes: [{ value: -100 }, { value: 0, gap: 800, ease: "outBack" }],
|
|
294
|
+
onUpdate: (v) => (sprite.y = v),
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
], {
|
|
298
|
+
// values[i] / velocities[i] correspond to layer i in definition order
|
|
299
|
+
onUpdate: (values, velocities) => {
|
|
300
|
+
sprite.x = values[0];
|
|
301
|
+
sprite.y = values[1];
|
|
401
302
|
},
|
|
303
|
+
onProgress: (p) => console.log(p),
|
|
402
304
|
});
|
|
403
305
|
```
|
|
404
306
|
|
|
405
|
-
|
|
307
|
+
### Spring
|
|
406
308
|
|
|
407
309
|
```ts
|
|
408
|
-
import { createSpring, lerpRgba, hexToRgba } from "anim-engine";
|
|
409
|
-
|
|
410
|
-
const fromColor = hexToRgba("#ff6b6b");
|
|
411
|
-
const toColor = hexToRgba("#4ecdc4");
|
|
412
|
-
|
|
413
310
|
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
|
-
},
|
|
311
|
+
to: () => mouseX,
|
|
312
|
+
stiffness: 180, damping: 12,
|
|
313
|
+
onUpdate: (v) => (sprite.x = v),
|
|
422
314
|
});
|
|
423
315
|
```
|
|
424
316
|
|
|
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.
|
|
317
|
+
### Smooth damp
|
|
434
318
|
|
|
435
319
|
```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",
|
|
320
|
+
createSmoothDamp({
|
|
321
|
+
to: () => 100, smoothTimeMs: 300,
|
|
445
322
|
onUpdate: (v) => (sprite.x = v),
|
|
446
|
-
})
|
|
323
|
+
});
|
|
447
324
|
```
|
|
448
325
|
|
|
449
|
-
|
|
326
|
+
### Lerp
|
|
450
327
|
|
|
451
328
|
```ts
|
|
452
|
-
|
|
329
|
+
createLerp({
|
|
330
|
+
to: () => 100, smoothTimeMs: 300,
|
|
331
|
+
onUpdate: (v) => (sprite.x = v),
|
|
332
|
+
});
|
|
333
|
+
```
|
|
453
334
|
|
|
454
|
-
|
|
335
|
+
### Smooth clamp
|
|
455
336
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
// ... physics, rendering
|
|
460
|
-
}
|
|
337
|
+
```ts
|
|
338
|
+
const clamp = createSmoothClamp(45);
|
|
339
|
+
clamp(1000); // ≈ 44.96 (asymptotic to 45)
|
|
461
340
|
```
|
|
462
341
|
|
|
463
|
-
|
|
342
|
+
---
|
|
464
343
|
|
|
465
344
|
## Easing
|
|
466
345
|
|
|
467
|
-
31
|
|
346
|
+
31 named presets plus custom cubic bezier:
|
|
468
347
|
|
|
469
348
|
```ts
|
|
470
|
-
|
|
349
|
+
type EaseName =
|
|
350
|
+
| "linear" | "inQuad" | "outQuad" | "inOutQuad"
|
|
351
|
+
| "inCubic" | "outCubic" | "inOutCubic"
|
|
352
|
+
| "inQuart" | "outQuart" | "inOutQuart"
|
|
353
|
+
| "inQuint" | "outQuint" | "inOutQuint"
|
|
354
|
+
| "inSine" | "outSine" | "inOutSine"
|
|
355
|
+
| "inExpo" | "outExpo" | "inOutExpo"
|
|
356
|
+
| "inCirc" | "outCirc" | "inOutCirc"
|
|
357
|
+
| "inBack" | "outBack" | "inOutBack"
|
|
358
|
+
| "inElastic" | "outElastic" | "inOutElastic"
|
|
359
|
+
| "inBounce" | "outBounce" | "inOutBounce"
|
|
360
|
+
|
|
361
|
+
type EaseFunction = (t: number) => number
|
|
362
|
+
```
|
|
471
363
|
|
|
472
|
-
|
|
364
|
+
Custom bezier:
|
|
473
365
|
|
|
474
|
-
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
to: 100,
|
|
478
|
-
durationMs: 1000,
|
|
479
|
-
ease: customEase,
|
|
480
|
-
onUpdate: (v) => (sprite.x = v),
|
|
481
|
-
});
|
|
366
|
+
```ts
|
|
367
|
+
const ease = cubicBezier(0.25, 0.1, 0.25, 1);
|
|
368
|
+
createAnimation({ from: 0, to: 100, durationMs: 1000, ease, ... });
|
|
482
369
|
```
|
|
483
370
|
|
|
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`.
|
|
371
|
+
---
|
|
487
372
|
|
|
488
373
|
## Dynamic values
|
|
489
374
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
### Timed animations (per-play)
|
|
375
|
+
`DynamicValue` (`number | (() => number)`) is resolved at different frequencies depending on the primitive:
|
|
493
376
|
|
|
494
|
-
|
|
377
|
+
- **Timed animations** — resolved once per `play()` call and cached for the run.
|
|
378
|
+
- **Continuous primitives** — resolved every frame (targets are live).
|
|
495
379
|
|
|
496
380
|
```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();
|
|
381
|
+
// Resolved at play time, cached for duration
|
|
382
|
+
createAnimation({
|
|
383
|
+
from: () => getX(),
|
|
384
|
+
durationMs: () => 500 / speed,
|
|
385
|
+
keyframes: [{ value: 0 }, { value: () => targetY, gap: 300 }],
|
|
386
|
+
}).play();
|
|
510
387
|
|
|
511
|
-
//
|
|
512
|
-
|
|
388
|
+
// Resolved every frame
|
|
389
|
+
createSpring({ to: () => mouseX, ... });
|
|
513
390
|
```
|
|
514
391
|
|
|
515
|
-
|
|
392
|
+
---
|
|
516
393
|
|
|
517
|
-
|
|
394
|
+
## Color
|
|
518
395
|
|
|
519
|
-
|
|
396
|
+
Oklab (perceptually uniform) color interpolation via `lerpRgba`.
|
|
520
397
|
|
|
521
398
|
```ts
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
399
|
+
const from = hexToRgba("#ff6b6b"); // [1, 0.42, 0.42, 1]
|
|
400
|
+
const to = hexToRgba("#4ecdc4");
|
|
401
|
+
|
|
402
|
+
createAnimation({
|
|
403
|
+
from: 0, to: 1, durationMs: 2000,
|
|
404
|
+
onUpdate: (t) => {
|
|
405
|
+
const [r, g, b, a] = lerpRgba(from, to, t);
|
|
406
|
+
sprite.setColor(r, g, b, a);
|
|
407
|
+
},
|
|
526
408
|
});
|
|
527
409
|
```
|
|
528
410
|
|
|
529
|
-
|
|
411
|
+
Accepts `#RGB`, `#RGBA`, `#RRGGBB`, `#RRGGBBAA` formats.
|
|
530
412
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
Performance comparison against GSAP (vitest bench, Apple Silicon M-series, Node 24). All easing functions are matched between libraries.
|
|
413
|
+
---
|
|
534
414
|
|
|
535
|
-
|
|
415
|
+
## Ticker
|
|
536
416
|
|
|
537
|
-
|
|
417
|
+
Does **not** auto-start. Explicit control:
|
|
538
418
|
|
|
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 |
|
|
419
|
+
```ts
|
|
420
|
+
import { getTicker } from "anim-engine";
|
|
551
421
|
|
|
552
|
-
|
|
422
|
+
// rAF mode
|
|
423
|
+
getTicker().start();
|
|
553
424
|
|
|
554
|
-
|
|
425
|
+
// Custom game loop
|
|
426
|
+
const ticker = getTicker();
|
|
427
|
+
function loop(dt: number) {
|
|
428
|
+
ticker.update(dt);
|
|
429
|
+
}
|
|
430
|
+
```
|
|
555
431
|
|
|
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 |
|
|
432
|
+
Stop with `getTicker().stop()`.
|
|
561
433
|
|
|
562
|
-
|
|
434
|
+
---
|
|
563
435
|
|
|
564
436
|
## Game engine integration
|
|
565
437
|
|
|
566
438
|
### PixiJS
|
|
567
439
|
|
|
568
440
|
```ts
|
|
569
|
-
import { Application, Sprite } from "pixi.js";
|
|
570
441
|
import { createAnimation, getTicker } from "anim-engine";
|
|
442
|
+
const ticker = getTicker();
|
|
571
443
|
|
|
572
|
-
|
|
573
|
-
await app.init({ width: 800, height: 600 });
|
|
574
|
-
|
|
575
|
-
const sprite = Sprite.from("texture.png");
|
|
576
|
-
app.stage.addChild(sprite);
|
|
577
|
-
|
|
578
|
-
// Sync anim-engine ticker to PixiJS ticker
|
|
579
|
-
const animEngineTicker = getTicker();
|
|
580
|
-
app.ticker.add((delta) => {
|
|
581
|
-
animEngineTicker.update(delta.deltaMS);
|
|
582
|
-
});
|
|
444
|
+
app.ticker.add((delta) => ticker.update(delta.deltaMS));
|
|
583
445
|
|
|
584
|
-
createAnimation({
|
|
585
|
-
from: 0,
|
|
586
|
-
to: 300,
|
|
587
|
-
durationMs: 2000,
|
|
588
|
-
ease: "outElastic",
|
|
446
|
+
createAnimation({ from: 0, to: 300, durationMs: 2000, ease: "outElastic",
|
|
589
447
|
onUpdate: (x) => (sprite.x = x),
|
|
590
448
|
}).play();
|
|
591
449
|
```
|
|
@@ -593,95 +451,39 @@ createAnimation({
|
|
|
593
451
|
### ThreeJS
|
|
594
452
|
|
|
595
453
|
```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
454
|
const clock = new THREE.Clock();
|
|
455
|
+
const ticker = getTicker();
|
|
611
456
|
|
|
612
457
|
function animate() {
|
|
613
458
|
requestAnimationFrame(animate);
|
|
614
|
-
|
|
459
|
+
ticker.update(clock.getDelta() * 1000);
|
|
615
460
|
renderer.render(scene, camera);
|
|
616
461
|
}
|
|
617
462
|
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
463
|
```
|
|
627
464
|
|
|
628
|
-
|
|
465
|
+
---
|
|
629
466
|
|
|
630
|
-
|
|
631
|
-
import { getTicker, createSpring } from "anim-engine";
|
|
467
|
+
## Benchmarks
|
|
632
468
|
|
|
633
|
-
|
|
469
|
+
vs GSAP (vitest bench, Apple Silicon M-series, Node 24). Matched easing functions.
|
|
634
470
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
471
|
+
| Benchmark | anim-engine | GSAP | Ratio |
|
|
472
|
+
|---|---|---|---|
|
|
473
|
+
| Single tween (cubic, 1000 frames) | 40,974 ops/s | 10,656 ops/s | 3.8× |
|
|
474
|
+
| Single tween (bezier, 1000 frames) | 20,190 ops/s | 11,948 ops/s | 1.7× |
|
|
475
|
+
| Keyframe (3 segments, 1000 frames) | 26,661 ops/s | 3,578 ops/s | 7.5× |
|
|
476
|
+
| 50 concurrent tweens (500 frames) | 866 ops/s | 436 ops/s | 2.0× |
|
|
477
|
+
| 200 concurrent tweens (500 frames) | 187 ops/s | 112 ops/s | 1.7× |
|
|
478
|
+
| 1000 concurrent tweens (500 frames) | 37 ops/s | 13 ops/s | 2.8× |
|
|
479
|
+
| 50-layer timeline (500 frames) | 684 ops/s | 406 ops/s | 1.7× |
|
|
480
|
+
| 50 tweens re-play (500 frames) | 523 ops/s | 133 ops/s | 3.9× |
|
|
638
481
|
|
|
639
|
-
|
|
640
|
-
// ... physics, rendering, etc.
|
|
482
|
+
vs GSAP `onUpdate` (apples-to-apples): **2.8–4.5× faster**.
|
|
641
483
|
|
|
642
|
-
|
|
643
|
-
}
|
|
644
|
-
requestAnimationFrame(gameLoop);
|
|
645
|
-
```
|
|
484
|
+
Run locally: `npm run bench`
|
|
646
485
|
|
|
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` |
|
|
486
|
+
---
|
|
685
487
|
|
|
686
488
|
## License
|
|
687
489
|
|