anim-engine 0.5.1 → 0.5.3

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.
Files changed (3) hide show
  1. package/README.md +180 -132
  2. package/SKILL.md +3 -3
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -20,7 +20,10 @@ import { createAnimation, getTicker } from "anim-engine";
20
20
  getTicker().start();
21
21
 
22
22
  const anim = createAnimation({
23
- from: 0, to: 100, durationMs: 1000, ease: "outCubic",
23
+ from: 0,
24
+ to: 100,
25
+ durationMs: 1000,
26
+ ease: "outCubic",
24
27
  onUpdate: (value) => (sprite.x = value),
25
28
  });
26
29
 
@@ -62,7 +65,7 @@ Anim Engine works strictly with numbers to stay renderer-agnostic. Each function
62
65
  - [Dynamic values](#dynamic-values)
63
66
  - [Color](#color)
64
67
  - [Ticker](#ticker)
65
- - [Game engine integration](#game-engine-integration)
68
+ - [Rendering Library integration](#rendering-library-integration)
66
69
  - [Benchmarks](#benchmarks)
67
70
 
68
71
  ---
@@ -71,10 +74,10 @@ Anim Engine works strictly with numbers to stay renderer-agnostic. Each function
71
74
 
72
75
  Two families with complementary shapes:
73
76
 
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 |
77
+ | Family | Functions | Lifespan |
78
+ | -------------- | ------------------------------------------------ | ----------------------------------------- |
79
+ | **Timed** | `createAnimation`, `createTimeline` | Fixed motion path, runs once per `play()` |
80
+ | **Continuous** | `createSpring`, `createSmoothDamp`, `createLerp` | Chases a live target until stopped |
78
81
 
79
82
  All return a control handle with `value`, `velocity`, `status`, and lifecycle methods.
80
83
 
@@ -86,54 +89,54 @@ All return a control handle with `value`, `velocity`, `status`, and lifecycle me
86
89
 
87
90
  ```ts
88
91
  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
- }
92
+ play: () => Promise<void>;
93
+ pause: () => void;
94
+ resume: () => void;
95
+ stop: () => void;
96
+ skipToEnd: () => void;
97
+ setProgress: (value: number) => void;
98
+
99
+ value: number;
100
+ velocity: number;
101
+ progress: number; // 0 → 1
102
+ status: "playing" | "paused" | "stopped";
103
+ durationMs: number;
104
+ };
102
105
  ```
103
106
 
104
107
  **Options — single tween:**
105
108
 
106
109
  ```ts
107
110
  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
- }
111
+ from: DynamicValue;
112
+ to: DynamicValue;
113
+ durationMs: DynamicValue;
114
+ ease?: EaseName | EaseFunction;
115
+ onStarted?: () => void;
116
+ onUpdate?: (value: number, velocity: number) => void;
117
+ onProgress?: (progress: number) => void;
118
+ onEnded?: () => void;
119
+ ticker?: ExternalTicker;
120
+ };
118
121
  ```
119
122
 
120
123
  **Options — keyframes:**
121
124
 
122
125
  ```ts
123
126
  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
130
- }
127
+ keyframes: Keyframe[];
128
+ onStarted?: () => void;
129
+ onUpdate?: (value: number, velocity: number) => void;
130
+ onProgress?: (progress: number) => void;
131
+ onEnded?: () => void;
132
+ ticker?: ExternalTicker;
133
+ };
131
134
 
132
135
  type Keyframe = {
133
- value: DynamicValue
134
- gap?: DynamicValue // ms from previous keyframe
135
- ease?: EaseName | EaseFunction
136
- }
136
+ value: DynamicValue;
137
+ gap?: DynamicValue; // ms from previous keyframe
138
+ ease?: EaseName | EaseFunction;
139
+ };
137
140
  ```
138
141
 
139
142
  ### Timed — `Timeline`
@@ -142,44 +145,44 @@ Layers multiple keyframe animations in parallel or sequence.
142
145
 
143
146
  ```ts
144
147
  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
- }
148
+ play: () => Promise<void>;
149
+ pause: () => void;
150
+ resume: () => void;
151
+ stop: () => void;
152
+ skipToEnd: () => void;
153
+ setProgress: (value: number) => void;
154
+
155
+ values: number[]; // one per layer
156
+ velocities: number[]; // one per layer
157
+ progress: number;
158
+ status: "playing" | "paused" | "stopped";
159
+ durationMs: number;
160
+ };
158
161
  ```
159
162
 
160
163
  ```ts
161
164
  type TimelineLayer =
162
- | { animation: KeyframeOptions; at: DynamicValue } // absolute position
163
- | { animation: KeyframeOptions; gap: number } // relative to previous layer end
165
+ | { animation: KeyframeOptions; at: DynamicValue } // absolute position
166
+ | { animation: KeyframeOptions; gap: number }; // relative to previous layer end
164
167
  ```
165
168
 
166
169
  ### Continuous — `Interpolation`
167
170
 
168
171
  ```ts
169
172
  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
- }
173
+ resume: () => void;
174
+ stop: () => void;
175
+ setValue: (value: number) => void;
176
+
177
+ value: number;
178
+ velocity: number;
179
+ status: "active" | "inactive";
180
+ };
178
181
  ```
179
182
 
180
183
  **Options:**
181
184
 
182
- ```ts
185
+ ````ts
183
186
  type SpringOptions = {
184
187
  to: () => number
185
188
  stiffness?: DynamicValue // default 180
@@ -209,28 +212,33 @@ type LerpOptions = {
209
212
  onEnded?: () => void
210
213
  ticker?: ExternalTicker
211
214
  }
212
- ```
215
+
216
+ ### Smooth clamp (pure function)
217
+
218
+ ```ts
219
+ createSmoothClamp(threshold: number): (input: number) => number
220
+ ````
213
221
 
214
222
  ### Common option patterns
215
223
 
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` |
224
+ | | `from` | `to` / target | `onUpdate` values | `durationMs` / `smoothTimeMs` |
225
+ | ----------------------------- | ------------------ | ----------------- | -------------------------------- | ----------------------------- |
226
+ | `createAnimation` (tween) | ✅ `DynamicValue` | ✅ `DynamicValue` | single `(value, velocity)` | ✅ `DynamicValue` |
227
+ | `createAnimation` (keyframes) | — (first keyframe) | — (last keyframe) | single `(value, velocity)` | — (sum of gaps) |
228
+ | `createTimeline` | — | — | multi `(values[], velocities[])` | — (computed) |
229
+ | `createSpring` | — (chases current) | ✅ `() => number` | single `(value, velocity)` | — (physics params) |
230
+ | `createSmoothDamp` | — (chases current) | ✅ `() => number` | single `(value, velocity)` | ✅ `DynamicValue` |
231
+ | `createLerp` | — (chases current) | ✅ `() => number` | single `(value, velocity)` | ✅ `DynamicValue` |
224
232
 
225
233
  ### Shared options
226
234
 
227
235
  ```ts
228
- type DynamicValue = number | (() => number)
236
+ type DynamicValue = number | (() => number);
229
237
 
230
238
  type ExternalTicker = {
231
- add: (handler: TickHandler) => void
232
- remove: (handler: TickHandler) => void
233
- }
239
+ add: (handler: TickHandler) => void;
240
+ remove: (handler: TickHandler) => void;
241
+ };
234
242
  ```
235
243
 
236
244
  ---
@@ -241,7 +249,10 @@ type ExternalTicker = {
241
249
 
242
250
  ```ts
243
251
  const anim = createAnimation({
244
- from: 0, to: 100, durationMs: 2000, ease: "outElastic",
252
+ from: 0,
253
+ to: 100,
254
+ durationMs: 2000,
255
+ ease: "outElastic",
245
256
  onUpdate: (v) => (sprite.x = v),
246
257
  });
247
258
  await anim.play();
@@ -251,11 +262,7 @@ await anim.play();
251
262
 
252
263
  ```ts
253
264
  createAnimation({
254
- keyframes: [
255
- { value: 0 },
256
- { value: 50, gap: 300, ease: "outCubic" },
257
- { value: 100, gap: 400 },
258
- ],
265
+ keyframes: [{ value: 0 }, { value: 50, gap: 300, ease: "outCubic" }, { value: 100, gap: 400 }],
259
266
  onUpdate: (v) => (sprite.x = v),
260
267
  }).play();
261
268
  ```
@@ -266,7 +273,8 @@ createAnimation({
266
273
  const anim = createAnimation({
267
274
  from: () => (forward ? 1 : 1.3),
268
275
  to: () => (forward ? 1.3 : 1),
269
- durationMs: 600, ease: "inOutSine",
276
+ durationMs: 600,
277
+ ease: "inOutSine",
270
278
  onUpdate: (v) => sprite.scale.set(v),
271
279
  });
272
280
 
@@ -279,29 +287,32 @@ for (let i = 0; i < 6; i++) {
279
287
  ### Timeline
280
288
 
281
289
  ```ts
282
- createTimeline([
283
- {
284
- at: 0,
285
- animation: {
286
- keyframes: [{ value: 0 }, { value: 1, gap: 500 }],
287
- onEnded: () => console.log("layer 1 done"),
290
+ createTimeline(
291
+ [
292
+ {
293
+ at: 0,
294
+ animation: {
295
+ keyframes: [{ value: 0 }, { value: 1, gap: 500 }],
296
+ onEnded: () => console.log("layer 1 done"),
297
+ },
288
298
  },
289
- },
299
+ {
300
+ at: 0,
301
+ animation: {
302
+ keyframes: [{ value: -100 }, { value: 0, gap: 800, ease: "outBack" }],
303
+ onUpdate: (v) => (sprite.y = v),
304
+ },
305
+ },
306
+ ],
290
307
  {
291
- at: 0,
292
- animation: {
293
- keyframes: [{ value: -100 }, { value: 0, gap: 800, ease: "outBack" }],
294
- onUpdate: (v) => (sprite.y = v),
308
+ // values[i] / velocities[i] correspond to layer i in definition order
309
+ onUpdate: (values, velocities) => {
310
+ sprite.x = values[0];
311
+ sprite.y = values[1];
295
312
  },
313
+ onProgress: (p) => console.log(p),
296
314
  },
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];
302
- },
303
- onProgress: (p) => console.log(p),
304
- });
315
+ );
305
316
  ```
306
317
 
307
318
  ### Spring
@@ -309,7 +320,8 @@ createTimeline([
309
320
  ```ts
310
321
  const spring = createSpring({
311
322
  to: () => mouseX,
312
- stiffness: 180, damping: 12,
323
+ stiffness: 180,
324
+ damping: 12,
313
325
  onUpdate: (v) => (sprite.x = v),
314
326
  });
315
327
  ```
@@ -318,7 +330,8 @@ const spring = createSpring({
318
330
 
319
331
  ```ts
320
332
  createSmoothDamp({
321
- to: () => 100, smoothTimeMs: 300,
333
+ to: () => 100,
334
+ smoothTimeMs: 300,
322
335
  onUpdate: (v) => (sprite.x = v),
323
336
  });
324
337
  ```
@@ -327,7 +340,8 @@ createSmoothDamp({
327
340
 
328
341
  ```ts
329
342
  createLerp({
330
- to: () => 100, smoothTimeMs: 300,
343
+ to: () => 100,
344
+ smoothTimeMs: 300,
331
345
  onUpdate: (v) => (sprite.x = v),
332
346
  });
333
347
  ```
@@ -347,18 +361,39 @@ clamp(1000); // ≈ 44.96 (asymptotic to 45)
347
361
 
348
362
  ```ts
349
363
  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
364
+ | "linear"
365
+ | "inQuad"
366
+ | "outQuad"
367
+ | "inOutQuad"
368
+ | "inCubic"
369
+ | "outCubic"
370
+ | "inOutCubic"
371
+ | "inQuart"
372
+ | "outQuart"
373
+ | "inOutQuart"
374
+ | "inQuint"
375
+ | "outQuint"
376
+ | "inOutQuint"
377
+ | "inSine"
378
+ | "outSine"
379
+ | "inOutSine"
380
+ | "inExpo"
381
+ | "outExpo"
382
+ | "inOutExpo"
383
+ | "inCirc"
384
+ | "outCirc"
385
+ | "inOutCirc"
386
+ | "inBack"
387
+ | "outBack"
388
+ | "inOutBack"
389
+ | "inElastic"
390
+ | "outElastic"
391
+ | "inOutElastic"
392
+ | "inBounce"
393
+ | "outBounce"
394
+ | "inOutBounce";
395
+
396
+ type EaseFunction = (t: number) => number;
362
397
  ```
363
398
 
364
399
  Custom bezier:
@@ -396,11 +431,20 @@ createSpring({ to: () => mouseX, ... });
396
431
  Oklab (perceptually uniform) color interpolation via `lerpRgba`.
397
432
 
398
433
  ```ts
399
- const from = hexToRgba("#ff6b6b"); // [1, 0.42, 0.42, 1]
434
+ type RgbaTuple = [number, number, number, number]
435
+
436
+ hexToRgba(hex: string): RgbaTuple
437
+ lerpRgba(from: RgbaTuple, to: RgbaTuple, t: number): RgbaTuple
438
+ ```
439
+
440
+ ```ts
441
+ const from = hexToRgba("#ff6b6b"); // [1, 0.42, 0.42, 1]
400
442
  const to = hexToRgba("#4ecdc4");
401
443
 
402
444
  createAnimation({
403
- from: 0, to: 1, durationMs: 2000,
445
+ from: 0,
446
+ to: 1,
447
+ durationMs: 2000,
404
448
  onUpdate: (t) => {
405
449
  const [r, g, b, a] = lerpRgba(from, to, t);
406
450
  sprite.setColor(r, g, b, a);
@@ -433,7 +477,7 @@ Stop with `getTicker().stop()`.
433
477
 
434
478
  ---
435
479
 
436
- ## Game engine integration
480
+ ## Rendering Library integration
437
481
 
438
482
  ### PixiJS
439
483
 
@@ -443,7 +487,11 @@ const ticker = getTicker();
443
487
 
444
488
  app.ticker.add((delta) => ticker.update(delta.deltaMS));
445
489
 
446
- createAnimation({ from: 0, to: 300, durationMs: 2000, ease: "outElastic",
490
+ createAnimation({
491
+ from: 0,
492
+ to: 300,
493
+ durationMs: 2000,
494
+ ease: "outElastic",
447
495
  onUpdate: (x) => (sprite.x = x),
448
496
  }).play();
449
497
  ```
@@ -468,16 +516,16 @@ animate();
468
516
 
469
517
  vs GSAP (vitest bench, Apple Silicon M-series, Node 24). Matched easing functions.
470
518
 
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× |
519
+ | Benchmark | anim-engine | GSAP | Ratio |
520
+ | ----------------------------------- | ------------ | ------------ | ----- |
521
+ | Single tween (cubic, 1000 frames) | 40,974 ops/s | 10,656 ops/s | 3.8× |
522
+ | Single tween (bezier, 1000 frames) | 20,190 ops/s | 11,948 ops/s | 1.7× |
523
+ | Keyframe (3 segments, 1000 frames) | 26,661 ops/s | 3,578 ops/s | 7.5× |
524
+ | 50 concurrent tweens (500 frames) | 866 ops/s | 436 ops/s | 2.0× |
525
+ | 200 concurrent tweens (500 frames) | 187 ops/s | 112 ops/s | 1.7× |
526
+ | 1000 concurrent tweens (500 frames) | 37 ops/s | 13 ops/s | 2.8× |
527
+ | 50-layer timeline (500 frames) | 684 ops/s | 406 ops/s | 1.7× |
528
+ | 50 tweens re-play (500 frames) | 523 ops/s | 133 ops/s | 3.9× |
481
529
 
482
530
  vs GSAP `onUpdate` (apples-to-apples): **2.8–4.5× faster**.
483
531
 
package/SKILL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: anim-engine
3
- description: Renderer-agnostic numeric animation library for JavaScript runtimes. Use when a user has installed 'anim-engine' and needs help writing animation code: tweens, keyframes, timelines, springs, smooth damp, lerp, or color interpolation. Covers all exported primitives, common recipes, game engine integration, and the ticker setup pattern.
3
+ description: Renderer-agnostic numeric animation library for JavaScript runtimes. Use when a user has installed 'anim-engine' and needs help writing animation code with tweens, keyframes, timelines, springs, smooth damp, lerp, or color interpolation. Covers all exported primitives, common recipes, game engine integration, and the ticker setup pattern.
4
4
  metadata:
5
5
  triggers: "anim-engine, animation, createAnimation, createTimeline, createSpring, createSmoothDamp, createLerp, createSmoothClamp, lerpRgba, hexToRgba, animation library, js animation, tween, keyframe animation, timeline animation, spring physics"
6
6
  ---
@@ -218,8 +218,8 @@ const timeline = createTimeline(
218
218
  {
219
219
  // values[i] / velocities[i] correspond to layer i in definition order
220
220
  onUpdate: (values, velocities) => {
221
- sprite.x = values[0]; // layer 1 (fade in)
222
- sprite.y = values[1]; // layer 2 (slide up)
221
+ sprite.x = values[0]; // layer 1 (fade in)
222
+ sprite.y = values[1]; // layer 2 (slide up)
223
223
  sprite.rotation = values[2]; // layer 3 (spin)
224
224
  },
225
225
  onProgress: (p) => console.log(p),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anim-engine",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "private": false,
5
5
  "description": "JavaScript library for animating numbers",
6
6
  "license": "MIT",