anim-engine 0.3.1 → 0.4.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 +8 -8
- package/dist/animation/create-animation.d.ts +1 -23
- package/dist/animation/create-animation.js +35 -41
- package/dist/animation/create-single-tween.d.ts +39 -0
- package/dist/animation/index.d.ts +1 -0
- package/dist/animation/runner.d.ts +5 -8
- package/dist/animation/runner.js +8 -62
- package/dist/animation/update.d.ts +1 -1
- package/dist/domain/animation.d.ts +40 -0
- package/dist/domain/color.d.ts +2 -0
- package/dist/domain/easing.d.ts +18 -0
- package/dist/{easing → domain}/easing.js +4 -37
- package/dist/domain/index.d.ts +10 -0
- package/dist/domain/interpolation.d.ts +35 -0
- package/dist/domain/resolve-value.d.ts +2 -0
- package/dist/domain/resolve-value.js +4 -0
- package/dist/domain/ticker.d.ts +8 -0
- package/dist/domain/timeline.d.ts +21 -0
- package/dist/index.d.ts +10 -17
- package/dist/index.js +7 -6
- package/dist/lerp/create-lerp.d.ts +1 -8
- package/dist/lerp/create-lerp.js +15 -15
- package/dist/lerp/index.d.ts +1 -0
- package/dist/lerp-rgba/hex-to-rgba.d.ts +13 -0
- package/dist/lerp-rgba/hex-to-rgba.js +48 -0
- package/dist/lerp-rgba/index.d.ts +2 -0
- package/dist/lerp-rgba/lerp-rgba.d.ts +13 -0
- package/dist/{color/lerp-oklab.js → lerp-rgba/lerp-rgba.js} +3 -48
- package/dist/smooth-clamp/{smooth-clamp.js → create-smooth-clamp.js} +1 -1
- package/dist/smooth-clamp/index.d.ts +1 -0
- package/dist/smooth-damp/create-smooth-damp.d.ts +1 -9
- package/dist/smooth-damp/create-smooth-damp.js +1 -1
- package/dist/smooth-damp/index.d.ts +1 -0
- package/dist/spring/create-spring.d.ts +1 -10
- package/dist/spring/create-spring.js +1 -1
- package/dist/spring/index.d.ts +1 -0
- package/dist/ticker/get-ticker.d.ts +16 -4
- package/dist/ticker/get-ticker.js +72 -2
- package/dist/ticker/index.d.ts +1 -0
- package/dist/timeline/create-timeline.d.ts +1 -21
- package/dist/timeline/create-timeline.js +8 -11
- package/dist/timeline/index.d.ts +1 -0
- package/package.json +3 -2
- package/dist/color/lerp-oklab.d.ts +0 -26
- package/dist/easing/easing.d.ts +0 -16
- package/dist/shared/internal.d.ts +0 -4
- package/dist/shared/types.d.ts +0 -28
- package/dist/ticker/ticker.d.ts +0 -22
- package/dist/ticker/ticker.js +0 -74
- /package/dist/smooth-clamp/{smooth-clamp.d.ts → create-smooth-clamp.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -81,7 +81,7 @@ By restricting itself to numeric values, the engine eliminates string parsing, c
|
|
|
81
81
|
| 🧩 **Composable models** | Animations, keyframes, and timelines compose cleanly — put tweens inside timelines, nest keyframes anywhere. |
|
|
82
82
|
| 🔄 **Continuous primitives** | Spring, smooth damp, and lerp chase live targets with zero setup — pass `() => value` for dynamic targets. |
|
|
83
83
|
| 🚫 **No GC pressure** | Zero object allocations in hot update paths. State is mutated in place. |
|
|
84
|
-
| 🎨 **Perceptual color** | Oklab interpolation via `
|
|
84
|
+
| 🎨 **Perceptual color** | Oklab interpolation via `lerpRgba` — no muddy browns. Compose it into any `onUpdate`. |
|
|
85
85
|
| 📐 **TypeScript-first** | Full type exports, exhaustive discriminated unions, no `any`. |
|
|
86
86
|
| 🔧 **Ticker control** | Bring your own game loop or use the built-in rAF ticker. Explicit — never auto-starts. |
|
|
87
87
|
|
|
@@ -112,7 +112,7 @@ Interpolations have `start()`/`stop()`/`kill()` controls, auto-start on creation
|
|
|
112
112
|
| Primitive | Returns | Description |
|
|
113
113
|
| ----------------------------------------- | -------------------- | --------------------------------------------- |
|
|
114
114
|
| [`createSmoothClamp`](#createsmoothclamp) | `(n: number) => n` | Asymptotic clamp — saturates toward threshold |
|
|
115
|
-
| [`
|
|
115
|
+
| [`lerpRgba` / `hexToRgba`](#color) | `RgbaTuple` / parser | Perceptually uniform color interpolation |
|
|
116
116
|
|
|
117
117
|
## Usage
|
|
118
118
|
|
|
@@ -381,7 +381,7 @@ Uses `threshold * (normalized / (1 + |normalized|))` for asymptotic saturation.
|
|
|
381
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.
|
|
382
382
|
|
|
383
383
|
```ts
|
|
384
|
-
import {
|
|
384
|
+
import { lerpRgba, hexToRgba } from "anim-engine";
|
|
385
385
|
|
|
386
386
|
hexToRgba("#ff6b6b"); // → [1, 0.42, 0.42, 1]
|
|
387
387
|
hexToRgba("#f80"); // → [1, 0.533, 0, 1] (shorthand)
|
|
@@ -396,7 +396,7 @@ createAnimation({
|
|
|
396
396
|
durationMs: 2000,
|
|
397
397
|
ease: "outCubic",
|
|
398
398
|
onUpdate: (t) => {
|
|
399
|
-
const [r, g, b, a] =
|
|
399
|
+
const [r, g, b, a] = lerpRgba(fromColor, toColor, t);
|
|
400
400
|
sprite.setColor(r, g, b, a);
|
|
401
401
|
},
|
|
402
402
|
});
|
|
@@ -405,7 +405,7 @@ createAnimation({
|
|
|
405
405
|
**With continuous primitives** — drive the blend factor via spring, damp, or lerp:
|
|
406
406
|
|
|
407
407
|
```ts
|
|
408
|
-
import { createSpring,
|
|
408
|
+
import { createSpring, lerpRgba, hexToRgba } from "anim-engine";
|
|
409
409
|
|
|
410
410
|
const fromColor = hexToRgba("#ff6b6b");
|
|
411
411
|
const toColor = hexToRgba("#4ecdc4");
|
|
@@ -416,7 +416,7 @@ const spring = createSpring({
|
|
|
416
416
|
stiffness: 180,
|
|
417
417
|
damping: 12,
|
|
418
418
|
onUpdate: (t) => {
|
|
419
|
-
const [r, g, b] =
|
|
419
|
+
const [r, g, b] = lerpRgba(fromColor, toColor, t);
|
|
420
420
|
sprite.setColor(r, g, b, 1);
|
|
421
421
|
},
|
|
422
422
|
});
|
|
@@ -658,7 +658,7 @@ requestAnimationFrame(gameLoop);
|
|
|
658
658
|
| `createSmoothClamp(threshold)` | Asymptotic clamp factory |
|
|
659
659
|
| `getTicker()` | Singleton ticker |
|
|
660
660
|
| `cubicBezier(p1x, p1y, p2x, p2y)` | Custom cubic bezier easing |
|
|
661
|
-
| `
|
|
661
|
+
| `lerpRgba(from, to, t)` | Oklab color interpolation |
|
|
662
662
|
| `hexToRgba(hex)` | Parse hex color to normalized RGBA |
|
|
663
663
|
|
|
664
664
|
### Type exports
|
|
@@ -681,7 +681,7 @@ requestAnimationFrame(gameLoop);
|
|
|
681
681
|
| `SmoothDampOptions` | `to`, `smoothTimeMs`, `maxSpeed?`, `precision?`, `onUpdate`, `onEnded` |
|
|
682
682
|
| `LerpOptions` | `to`, `smoothTimeMs`, `precision?`, `onUpdate`, `onEnded` |
|
|
683
683
|
| `RgbaTuple` | `readonly [number, number, number, number]` |
|
|
684
|
-
| `
|
|
684
|
+
| `Ticker` | `start`, `stop`, `update`, `add`, `remove` |
|
|
685
685
|
|
|
686
686
|
## License
|
|
687
687
|
|
|
@@ -1,24 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export type SingleTweenOptions = {
|
|
3
|
-
from: DynamicValue;
|
|
4
|
-
to: DynamicValue;
|
|
5
|
-
durationMs: DynamicValue;
|
|
6
|
-
ease?: EaseName | EaseFunction;
|
|
7
|
-
onStarted?: () => void;
|
|
8
|
-
onUpdate?: (value: number, velocity: number) => void;
|
|
9
|
-
onEnded?: () => void;
|
|
10
|
-
};
|
|
11
|
-
export type Keyframe = {
|
|
12
|
-
value: DynamicValue;
|
|
13
|
-
ease?: EaseName | EaseFunction;
|
|
14
|
-
gap?: DynamicValue;
|
|
15
|
-
};
|
|
16
|
-
export type KeyframeAnimationOptions = {
|
|
17
|
-
keyframes: Keyframe[];
|
|
18
|
-
onStarted?: () => void;
|
|
19
|
-
onUpdate?: (value: number, velocity: number) => void;
|
|
20
|
-
onProgress?: (progress: number) => void;
|
|
21
|
-
onEnded?: () => void;
|
|
22
|
-
};
|
|
23
|
-
export type AnimationOptions = SingleTweenOptions | KeyframeAnimationOptions;
|
|
1
|
+
import { AnimationOptions, Animation } from '../domain';
|
|
24
2
|
export declare const createAnimation: (options: AnimationOptions) => Animation;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveEasing } from "../domain/easing.js";
|
|
2
|
+
import { resolveValue } from "../domain/resolve-value.js";
|
|
2
3
|
import { getTicker } from "../ticker/get-ticker.js";
|
|
3
4
|
import { createKeyframeRunner, createTweenRunner } from "./runner.js";
|
|
4
5
|
//#region src/animation/create-animation.ts
|
|
5
|
-
var resolveEasing = (ease) => typeof ease === "function" ? ease : easingFunctions[ease];
|
|
6
|
-
var resolveValue = (v) => typeof v === "function" ? v() : v;
|
|
7
6
|
var isKeyframeMode = (options) => {
|
|
8
7
|
return "keyframes" in options && Array.isArray(options.keyframes);
|
|
9
8
|
};
|
|
@@ -11,23 +10,20 @@ var createAnimation = (options) => {
|
|
|
11
10
|
if (isKeyframeMode(options)) return createKeyframeAnimation(options);
|
|
12
11
|
return createSingleTween(options);
|
|
13
12
|
};
|
|
14
|
-
var createSingleTween = (
|
|
15
|
-
const easeName = options.ease ?? "inOutSine";
|
|
16
|
-
const { onStarted, onUpdate, onEnded } = options;
|
|
17
|
-
const rawFrom = options.from;
|
|
18
|
-
const rawTo = options.to;
|
|
19
|
-
const rawDurationMs = options.durationMs;
|
|
13
|
+
var createSingleTween = ({ onStarted, onUpdate, onProgress, onEnded, from: rawFrom, to: rawTo, durationMs: rawDurationMs, ease: easeName = "inOutSine" }) => {
|
|
20
14
|
let cachedDurationMs = resolveValue(rawDurationMs);
|
|
21
15
|
let status = "stopped";
|
|
16
|
+
const hasDynamicProperty = typeof rawFrom === "function" || typeof rawTo === "function" || typeof rawDurationMs === "function";
|
|
22
17
|
let resolvePromise;
|
|
23
18
|
const ticker = getTicker();
|
|
24
|
-
|
|
25
|
-
const finish = () => {
|
|
19
|
+
const handleEnded = () => {
|
|
26
20
|
status = "stopped";
|
|
27
21
|
ticker.remove(runner);
|
|
28
|
-
resolvePromise?.(
|
|
22
|
+
resolvePromise?.();
|
|
23
|
+
onEnded?.();
|
|
29
24
|
resolvePromise = void 0;
|
|
30
25
|
};
|
|
26
|
+
let runner;
|
|
31
27
|
const buildRunner = () => {
|
|
32
28
|
const from = resolveValue(rawFrom);
|
|
33
29
|
const to = resolveValue(rawTo);
|
|
@@ -39,15 +35,14 @@ var createSingleTween = (options) => {
|
|
|
39
35
|
easeFn: resolveEasing(easeName),
|
|
40
36
|
onStarted,
|
|
41
37
|
onUpdate,
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
onProgress,
|
|
39
|
+
onEnded: handleEnded
|
|
44
40
|
});
|
|
45
41
|
};
|
|
46
|
-
const hasDynamic = typeof rawFrom === "function" || typeof rawTo === "function" || typeof rawDurationMs === "function";
|
|
47
42
|
runner = buildRunner();
|
|
48
43
|
const play = () => {
|
|
49
44
|
if (status === "dead") throw new Error("Cannot play a dead animation");
|
|
50
|
-
if (
|
|
45
|
+
if (hasDynamicProperty) runner = buildRunner();
|
|
51
46
|
else runner.reset();
|
|
52
47
|
const promise = new Promise((resolve) => {
|
|
53
48
|
resolvePromise = resolve;
|
|
@@ -70,7 +65,7 @@ var createSingleTween = (options) => {
|
|
|
70
65
|
const stop = () => {
|
|
71
66
|
status = "stopped";
|
|
72
67
|
ticker.remove(runner);
|
|
73
|
-
resolvePromise?.(
|
|
68
|
+
resolvePromise?.();
|
|
74
69
|
resolvePromise = void 0;
|
|
75
70
|
};
|
|
76
71
|
const skipToEnd = () => {
|
|
@@ -78,7 +73,7 @@ var createSingleTween = (options) => {
|
|
|
78
73
|
if (status === "playing" || status === "paused") onEnded?.();
|
|
79
74
|
status = "stopped";
|
|
80
75
|
ticker.remove(runner);
|
|
81
|
-
resolvePromise?.(
|
|
76
|
+
resolvePromise?.();
|
|
82
77
|
resolvePromise = void 0;
|
|
83
78
|
};
|
|
84
79
|
const kill = () => {
|
|
@@ -86,7 +81,11 @@ var createSingleTween = (options) => {
|
|
|
86
81
|
ticker.remove(runner);
|
|
87
82
|
resolvePromise = void 0;
|
|
88
83
|
};
|
|
89
|
-
const
|
|
84
|
+
const setProgress = (value) => {
|
|
85
|
+
if (status === "playing") pause();
|
|
86
|
+
runner.evaluate(Math.max(0, Math.min(1, value)));
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
90
89
|
play,
|
|
91
90
|
pause,
|
|
92
91
|
resume,
|
|
@@ -102,10 +101,7 @@ var createSingleTween = (options) => {
|
|
|
102
101
|
get progress() {
|
|
103
102
|
return runner.progress;
|
|
104
103
|
},
|
|
105
|
-
setProgress
|
|
106
|
-
if (status === "playing") pause();
|
|
107
|
-
runner.evaluate(Math.max(0, Math.min(1, value)));
|
|
108
|
-
},
|
|
104
|
+
setProgress,
|
|
109
105
|
get status() {
|
|
110
106
|
return status;
|
|
111
107
|
},
|
|
@@ -113,10 +109,8 @@ var createSingleTween = (options) => {
|
|
|
113
109
|
return cachedDurationMs;
|
|
114
110
|
}
|
|
115
111
|
};
|
|
116
|
-
return controls;
|
|
117
112
|
};
|
|
118
|
-
var createKeyframeAnimation = (
|
|
119
|
-
const { keyframes: rawKeyframes, onStarted, onUpdate, onProgress, onEnded } = options;
|
|
113
|
+
var createKeyframeAnimation = ({ keyframes: rawKeyframes, onStarted, onUpdate, onProgress, onEnded }) => {
|
|
120
114
|
const resolveKeyframeGaps = () => {
|
|
121
115
|
let total = 0;
|
|
122
116
|
for (let i = 1; i < rawKeyframes.length; i++) total += resolveValue(rawKeyframes[i].gap ?? 0);
|
|
@@ -125,14 +119,16 @@ var createKeyframeAnimation = (options) => {
|
|
|
125
119
|
let cachedDurationMs = resolveKeyframeGaps();
|
|
126
120
|
let status = "stopped";
|
|
127
121
|
let resolvePromise;
|
|
122
|
+
const hasDynamicProperty = rawKeyframes.some((kf) => typeof kf.value === "function" || typeof kf.gap === "function");
|
|
128
123
|
const ticker = getTicker();
|
|
129
|
-
|
|
130
|
-
const finish = () => {
|
|
124
|
+
const handleEnded = () => {
|
|
131
125
|
status = "stopped";
|
|
132
126
|
ticker.remove(runner);
|
|
133
|
-
resolvePromise?.(
|
|
127
|
+
resolvePromise?.();
|
|
128
|
+
onEnded?.();
|
|
134
129
|
resolvePromise = void 0;
|
|
135
130
|
};
|
|
131
|
+
let runner;
|
|
136
132
|
const buildRunner = () => {
|
|
137
133
|
return createKeyframeRunner({
|
|
138
134
|
keyframes: rawKeyframes.map((kf, i) => ({
|
|
@@ -143,15 +139,13 @@ var createKeyframeAnimation = (options) => {
|
|
|
143
139
|
onStarted,
|
|
144
140
|
onUpdate,
|
|
145
141
|
onProgress,
|
|
146
|
-
onEnded
|
|
147
|
-
onComplete: finish
|
|
142
|
+
onEnded: handleEnded
|
|
148
143
|
});
|
|
149
144
|
};
|
|
150
|
-
const hasDynamic = rawKeyframes.some((kf) => typeof kf.value === "function" || typeof kf.gap === "function");
|
|
151
145
|
runner = buildRunner();
|
|
152
146
|
const play = () => {
|
|
153
147
|
if (status === "dead") throw new Error("Cannot play a dead animation");
|
|
154
|
-
if (
|
|
148
|
+
if (hasDynamicProperty) {
|
|
155
149
|
runner = buildRunner();
|
|
156
150
|
cachedDurationMs = resolveKeyframeGaps();
|
|
157
151
|
} else runner.reset();
|
|
@@ -176,7 +170,7 @@ var createKeyframeAnimation = (options) => {
|
|
|
176
170
|
const stop = () => {
|
|
177
171
|
status = "stopped";
|
|
178
172
|
ticker.remove(runner);
|
|
179
|
-
resolvePromise?.(
|
|
173
|
+
resolvePromise?.();
|
|
180
174
|
resolvePromise = void 0;
|
|
181
175
|
};
|
|
182
176
|
const skipToEnd = () => {
|
|
@@ -184,7 +178,7 @@ var createKeyframeAnimation = (options) => {
|
|
|
184
178
|
if (status === "playing" || status === "paused") onEnded?.();
|
|
185
179
|
status = "stopped";
|
|
186
180
|
ticker.remove(runner);
|
|
187
|
-
resolvePromise?.(
|
|
181
|
+
resolvePromise?.();
|
|
188
182
|
resolvePromise = void 0;
|
|
189
183
|
};
|
|
190
184
|
const kill = () => {
|
|
@@ -192,7 +186,11 @@ var createKeyframeAnimation = (options) => {
|
|
|
192
186
|
ticker.remove(runner);
|
|
193
187
|
resolvePromise = void 0;
|
|
194
188
|
};
|
|
195
|
-
const
|
|
189
|
+
const setProgress = (value) => {
|
|
190
|
+
if (status === "playing") pause();
|
|
191
|
+
runner.evaluate(Math.max(0, Math.min(1, value)));
|
|
192
|
+
};
|
|
193
|
+
return {
|
|
196
194
|
play,
|
|
197
195
|
pause,
|
|
198
196
|
resume,
|
|
@@ -208,10 +206,7 @@ var createKeyframeAnimation = (options) => {
|
|
|
208
206
|
get progress() {
|
|
209
207
|
return runner.progress;
|
|
210
208
|
},
|
|
211
|
-
setProgress
|
|
212
|
-
if (status === "playing") pause();
|
|
213
|
-
runner.evaluate(Math.max(0, Math.min(1, value)));
|
|
214
|
-
},
|
|
209
|
+
setProgress,
|
|
215
210
|
get status() {
|
|
216
211
|
return status;
|
|
217
212
|
},
|
|
@@ -219,7 +214,6 @@ var createKeyframeAnimation = (options) => {
|
|
|
219
214
|
return cachedDurationMs;
|
|
220
215
|
}
|
|
221
216
|
};
|
|
222
|
-
return controls;
|
|
223
217
|
};
|
|
224
218
|
//#endregion
|
|
225
219
|
export { createAnimation };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { EaseName, AnimationStatus, DynamicValue, EaseFunction } from '../domain';
|
|
2
|
+
export type SingleTweenOptions = {
|
|
3
|
+
from: DynamicValue;
|
|
4
|
+
to: DynamicValue;
|
|
5
|
+
durationMs: DynamicValue;
|
|
6
|
+
ease?: EaseName | EaseFunction;
|
|
7
|
+
onStarted?: () => void;
|
|
8
|
+
onUpdate?: (value: number, velocity: number) => void;
|
|
9
|
+
onProgress?: (progress: number) => void;
|
|
10
|
+
onEnded?: () => void;
|
|
11
|
+
};
|
|
12
|
+
export type Keyframe = {
|
|
13
|
+
value: DynamicValue;
|
|
14
|
+
ease?: EaseName | EaseFunction;
|
|
15
|
+
gap?: DynamicValue;
|
|
16
|
+
};
|
|
17
|
+
export type KeyframeAnimationOptions = {
|
|
18
|
+
keyframes: Keyframe[];
|
|
19
|
+
onStarted?: () => void;
|
|
20
|
+
onUpdate?: (value: number, velocity: number) => void;
|
|
21
|
+
onProgress?: (progress: number) => void;
|
|
22
|
+
onEnded?: () => void;
|
|
23
|
+
};
|
|
24
|
+
export type AnimationOptions = SingleTweenOptions | KeyframeAnimationOptions;
|
|
25
|
+
export type Animation = {
|
|
26
|
+
play: () => Promise<void>;
|
|
27
|
+
pause: () => void;
|
|
28
|
+
resume: () => void;
|
|
29
|
+
stop: () => void;
|
|
30
|
+
skipToEnd: () => void;
|
|
31
|
+
kill: () => void;
|
|
32
|
+
currentValue: number;
|
|
33
|
+
velocity: number;
|
|
34
|
+
progress: number;
|
|
35
|
+
setProgress: (value: number) => void;
|
|
36
|
+
status: AnimationStatus;
|
|
37
|
+
durationMs: number;
|
|
38
|
+
};
|
|
39
|
+
export declare const createSingleTween: ({ onStarted, onUpdate, onProgress, onEnded, from: rawFrom, to: rawTo, durationMs: rawDurationMs, ease: easeName, }: SingleTweenOptions) => Animation;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createAnimation } from './create-animation';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EaseFunction } from '../
|
|
1
|
+
import { EaseFunction } from '../domain';
|
|
2
2
|
export type Runner = {
|
|
3
3
|
(deltaMs: number): boolean;
|
|
4
4
|
evaluate: (progress: number) => number;
|
|
@@ -7,8 +7,6 @@ export type Runner = {
|
|
|
7
7
|
velocity: number;
|
|
8
8
|
progress: number;
|
|
9
9
|
onStarted: (() => void) | undefined;
|
|
10
|
-
onUpdate: ((value: number, velocity: number) => void) | undefined;
|
|
11
|
-
onProgress: ((progress: number) => void) | undefined;
|
|
12
10
|
onEnded: (() => void) | undefined;
|
|
13
11
|
};
|
|
14
12
|
export type TweenRunnerConfig = {
|
|
@@ -18,10 +16,10 @@ export type TweenRunnerConfig = {
|
|
|
18
16
|
easeFn: EaseFunction;
|
|
19
17
|
onStarted?: () => void;
|
|
20
18
|
onUpdate?: (value: number, velocity: number) => void;
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
onProgress?: (progress: number) => void;
|
|
20
|
+
onEnded: () => void;
|
|
23
21
|
};
|
|
24
|
-
export declare const createTweenRunner: (
|
|
22
|
+
export declare const createTweenRunner: ({ from, to, durationMs, easeFn, onStarted, onUpdate, onProgress, onEnded, }: TweenRunnerConfig) => Runner;
|
|
25
23
|
export type KeyframeRunnerConfig = {
|
|
26
24
|
keyframes: {
|
|
27
25
|
value: number;
|
|
@@ -32,6 +30,5 @@ export type KeyframeRunnerConfig = {
|
|
|
32
30
|
onUpdate?: (value: number, velocity: number) => void;
|
|
33
31
|
onProgress?: (progress: number) => void;
|
|
34
32
|
onEnded?: () => void;
|
|
35
|
-
onComplete?: () => void;
|
|
36
33
|
};
|
|
37
|
-
export declare const createKeyframeRunner: (
|
|
34
|
+
export declare const createKeyframeRunner: ({ keyframes, onStarted, onUpdate, onProgress, onEnded, }: KeyframeRunnerConfig) => Runner;
|
package/dist/animation/runner.js
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { updateTween } from "./update.js";
|
|
2
2
|
//#region src/animation/runner.ts
|
|
3
|
-
var
|
|
4
|
-
var createTweenRunner = (
|
|
5
|
-
const { from, to, durationMs, easeFn } = config;
|
|
6
|
-
const onStarted = config.onStarted;
|
|
7
|
-
const onUpdate = config.onUpdate ?? noop;
|
|
8
|
-
const onEnded = config.onEnded;
|
|
9
|
-
const onComplete = config.onComplete;
|
|
3
|
+
var noOp = () => {};
|
|
4
|
+
var createTweenRunner = ({ from, to, durationMs, easeFn, onStarted, onUpdate = noOp, onProgress = noOp, onEnded }) => {
|
|
10
5
|
const state = {
|
|
11
6
|
progress: 0,
|
|
12
7
|
currentValue: from,
|
|
@@ -16,10 +11,8 @@ var createTweenRunner = (config) => {
|
|
|
16
11
|
const step = (deltaMs) => {
|
|
17
12
|
const completed = updateTween(state, deltaMs, durationMs, easeFn, from, to);
|
|
18
13
|
onUpdate(state.currentValue, state.velocity);
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
onComplete?.();
|
|
22
|
-
}
|
|
14
|
+
onProgress(state.progress);
|
|
15
|
+
if (completed) onEnded();
|
|
23
16
|
return completed;
|
|
24
17
|
};
|
|
25
18
|
const evaluate = (progress) => {
|
|
@@ -57,18 +50,11 @@ var createTweenRunner = (config) => {
|
|
|
57
50
|
configurable: true
|
|
58
51
|
});
|
|
59
52
|
runner.onStarted = onStarted;
|
|
60
|
-
runner.onUpdate = config.onUpdate;
|
|
61
|
-
runner.onProgress = void 0;
|
|
62
53
|
runner.onEnded = onEnded;
|
|
63
54
|
return runner;
|
|
64
55
|
};
|
|
65
|
-
var createKeyframeRunner = (
|
|
66
|
-
|
|
67
|
-
const onStarted = config.onStarted;
|
|
68
|
-
const onUpdate = config.onUpdate ?? noop;
|
|
69
|
-
const onProgress = config.onProgress ?? noop;
|
|
70
|
-
const onEnded = config.onEnded;
|
|
71
|
-
const onComplete = config.onComplete;
|
|
56
|
+
var createKeyframeRunner = ({ keyframes, onStarted, onUpdate = noOp, onProgress = noOp, onEnded }) => {
|
|
57
|
+
if (keyframes.length < 2) throw new Error("Keyframe animation must have at least 2 keyframes");
|
|
72
58
|
const segments = [];
|
|
73
59
|
for (let i = 0; i < keyframes.length - 1; i++) {
|
|
74
60
|
const cur = keyframes[i];
|
|
@@ -92,44 +78,7 @@ var createKeyframeRunner = (config) => {
|
|
|
92
78
|
let segmentElapsed = 0;
|
|
93
79
|
let segmentProgress = 0;
|
|
94
80
|
let runner;
|
|
95
|
-
|
|
96
|
-
const complete = () => {
|
|
97
|
-
onEnded?.();
|
|
98
|
-
onComplete?.();
|
|
99
|
-
};
|
|
100
|
-
const step = (_deltaMs) => {
|
|
101
|
-
onUpdate(currentValue, 0);
|
|
102
|
-
onProgress(1);
|
|
103
|
-
complete();
|
|
104
|
-
return true;
|
|
105
|
-
};
|
|
106
|
-
const evaluate = (prog) => {
|
|
107
|
-
onUpdate(currentValue, 0);
|
|
108
|
-
onProgress(Math.max(0, Math.min(1, prog)));
|
|
109
|
-
return currentValue;
|
|
110
|
-
};
|
|
111
|
-
runner = step;
|
|
112
|
-
runner.evaluate = evaluate;
|
|
113
|
-
runner.reset = () => {};
|
|
114
|
-
Object.defineProperty(runner, "currentValue", {
|
|
115
|
-
get: () => currentValue,
|
|
116
|
-
configurable: true
|
|
117
|
-
});
|
|
118
|
-
Object.defineProperty(runner, "velocity", {
|
|
119
|
-
get: () => 0,
|
|
120
|
-
configurable: true
|
|
121
|
-
});
|
|
122
|
-
Object.defineProperty(runner, "progress", {
|
|
123
|
-
get: () => 1,
|
|
124
|
-
configurable: true
|
|
125
|
-
});
|
|
126
|
-
runner.onStarted = onStarted;
|
|
127
|
-
runner.onUpdate = config.onUpdate;
|
|
128
|
-
runner.onProgress = config.onProgress;
|
|
129
|
-
runner.onEnded = onEnded;
|
|
130
|
-
return runner;
|
|
131
|
-
}
|
|
132
|
-
const step = (deltaMs) => {
|
|
81
|
+
const update = (deltaMs) => {
|
|
133
82
|
const segment = segments[currentSegmentIndex];
|
|
134
83
|
segmentElapsed += deltaMs;
|
|
135
84
|
segmentProgress += deltaMs / segment.durationMs;
|
|
@@ -155,7 +104,6 @@ var createKeyframeRunner = (config) => {
|
|
|
155
104
|
onProgress(globalProgress);
|
|
156
105
|
} else {
|
|
157
106
|
onEnded?.();
|
|
158
|
-
onComplete?.();
|
|
159
107
|
return true;
|
|
160
108
|
}
|
|
161
109
|
return false;
|
|
@@ -195,7 +143,7 @@ var createKeyframeRunner = (config) => {
|
|
|
195
143
|
segmentElapsed = 0;
|
|
196
144
|
segmentProgress = 0;
|
|
197
145
|
};
|
|
198
|
-
runner =
|
|
146
|
+
runner = update;
|
|
199
147
|
runner.evaluate = evaluate;
|
|
200
148
|
runner.reset = reset;
|
|
201
149
|
Object.defineProperty(runner, "currentValue", {
|
|
@@ -211,8 +159,6 @@ var createKeyframeRunner = (config) => {
|
|
|
211
159
|
configurable: true
|
|
212
160
|
});
|
|
213
161
|
runner.onStarted = onStarted;
|
|
214
|
-
runner.onUpdate = config.onUpdate;
|
|
215
|
-
runner.onProgress = config.onProgress;
|
|
216
162
|
runner.onEnded = onEnded;
|
|
217
163
|
return runner;
|
|
218
164
|
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { EaseFunction, EaseName } from './easing';
|
|
2
|
+
import { DynamicValue } from './resolve-value';
|
|
3
|
+
export type AnimationStatus = "playing" | "paused" | "stopped" | "dead";
|
|
4
|
+
export type SingleTweenOptions = {
|
|
5
|
+
from: DynamicValue;
|
|
6
|
+
to: DynamicValue;
|
|
7
|
+
durationMs: DynamicValue;
|
|
8
|
+
ease?: EaseName | EaseFunction;
|
|
9
|
+
onStarted?: () => void;
|
|
10
|
+
onUpdate?: (value: number, velocity: number) => void;
|
|
11
|
+
onProgress?: (progress: number) => void;
|
|
12
|
+
onEnded?: () => void;
|
|
13
|
+
};
|
|
14
|
+
export type Keyframe = {
|
|
15
|
+
value: DynamicValue;
|
|
16
|
+
ease?: EaseName | EaseFunction;
|
|
17
|
+
gap?: DynamicValue;
|
|
18
|
+
};
|
|
19
|
+
export type KeyframeAnimationOptions = {
|
|
20
|
+
keyframes: Keyframe[];
|
|
21
|
+
onStarted?: () => void;
|
|
22
|
+
onUpdate?: (value: number, velocity: number) => void;
|
|
23
|
+
onProgress?: (progress: number) => void;
|
|
24
|
+
onEnded?: () => void;
|
|
25
|
+
};
|
|
26
|
+
export type AnimationOptions = SingleTweenOptions | KeyframeAnimationOptions;
|
|
27
|
+
export type Animation = {
|
|
28
|
+
play: () => Promise<void>;
|
|
29
|
+
pause: () => void;
|
|
30
|
+
resume: () => void;
|
|
31
|
+
stop: () => void;
|
|
32
|
+
skipToEnd: () => void;
|
|
33
|
+
kill: () => void;
|
|
34
|
+
currentValue: number;
|
|
35
|
+
velocity: number;
|
|
36
|
+
progress: number;
|
|
37
|
+
setProgress: (value: number) => void;
|
|
38
|
+
status: AnimationStatus;
|
|
39
|
+
durationMs: number;
|
|
40
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type EaseFunction = (t: number) => number;
|
|
2
|
+
export declare const resolveEasing: (ease: EaseName | EaseFunction) => EaseFunction;
|
|
3
|
+
/** All 31 named easing identifiers, ordered by type. */
|
|
4
|
+
export declare const EASE_NAMES: readonly ["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"];
|
|
5
|
+
export type EaseName = (typeof EASE_NAMES)[number];
|
|
6
|
+
export declare const EASING_FUNCTIONS: Record<EaseName, EaseFunction>;
|
|
7
|
+
/**
|
|
8
|
+
* Cubic bezier easing. Builds a pre-computed lookup table at construction time
|
|
9
|
+
* for O(log n) binary search at runtime — no Newton iteration, no allocation
|
|
10
|
+
* per frame.
|
|
11
|
+
*
|
|
12
|
+
* @param p1x - First control point x coordinate.
|
|
13
|
+
* @param p1y - First control point y coordinate.
|
|
14
|
+
* @param p2x - Second control point x coordinate.
|
|
15
|
+
* @param p2y - Second control point y coordinate.
|
|
16
|
+
* @returns An EaseFunction suitable for use with animate() or createTimeline().
|
|
17
|
+
*/
|
|
18
|
+
export declare const cubicBezier: (p1x: number, p1y: number, p2x: number, p2y: number) => EaseFunction;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
//#region src/
|
|
1
|
+
//#region src/domain/easing.ts
|
|
2
|
+
var resolveEasing = (ease) => typeof ease === "function" ? ease : EASING_FUNCTIONS[ease];
|
|
2
3
|
var pow = Math.pow;
|
|
3
4
|
var sqrt = Math.sqrt;
|
|
4
5
|
var sin = Math.sin;
|
|
@@ -17,41 +18,7 @@ var bounceOut = (x) => {
|
|
|
17
18
|
else if (x < 2.5 / d1) return n1 * (x -= 2.25 / d1) * x + .9375;
|
|
18
19
|
else return n1 * (x -= 2.625 / d1) * x + .984375;
|
|
19
20
|
};
|
|
20
|
-
|
|
21
|
-
var EASE_NAMES = [
|
|
22
|
-
"linear",
|
|
23
|
-
"inQuad",
|
|
24
|
-
"outQuad",
|
|
25
|
-
"inOutQuad",
|
|
26
|
-
"inCubic",
|
|
27
|
-
"outCubic",
|
|
28
|
-
"inOutCubic",
|
|
29
|
-
"inQuart",
|
|
30
|
-
"outQuart",
|
|
31
|
-
"inOutQuart",
|
|
32
|
-
"inQuint",
|
|
33
|
-
"outQuint",
|
|
34
|
-
"inOutQuint",
|
|
35
|
-
"inSine",
|
|
36
|
-
"outSine",
|
|
37
|
-
"inOutSine",
|
|
38
|
-
"inExpo",
|
|
39
|
-
"outExpo",
|
|
40
|
-
"inOutExpo",
|
|
41
|
-
"inCirc",
|
|
42
|
-
"outCirc",
|
|
43
|
-
"inOutCirc",
|
|
44
|
-
"inBack",
|
|
45
|
-
"outBack",
|
|
46
|
-
"inOutBack",
|
|
47
|
-
"inElastic",
|
|
48
|
-
"outElastic",
|
|
49
|
-
"inOutElastic",
|
|
50
|
-
"inBounce",
|
|
51
|
-
"outBounce",
|
|
52
|
-
"inOutBounce"
|
|
53
|
-
];
|
|
54
|
-
var easingFunctions = {
|
|
21
|
+
var EASING_FUNCTIONS = {
|
|
55
22
|
linear: (x) => x,
|
|
56
23
|
inQuad: (x) => x * x,
|
|
57
24
|
outQuad: (x) => 1 - (1 - x) * (1 - x),
|
|
@@ -151,4 +118,4 @@ var cubicBezier = (p1x, p1y, p2x, p2y) => {
|
|
|
151
118
|
};
|
|
152
119
|
};
|
|
153
120
|
//#endregion
|
|
154
|
-
export {
|
|
121
|
+
export { EASING_FUNCTIONS, cubicBezier, resolveEasing };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type { AnimationStatus } from './animation';
|
|
2
|
+
export type { InterpolationStatus, Interpolation, LerpOptions, SmoothDampOptions, SpringOptions, } from './interpolation';
|
|
3
|
+
export type { EaseName, EaseFunction } from './easing';
|
|
4
|
+
export { EASE_NAMES, cubicBezier, EASING_FUNCTIONS, resolveEasing } from './easing';
|
|
5
|
+
export type { Ticker, TickHandler } from './ticker';
|
|
6
|
+
export { resolveValue } from './resolve-value';
|
|
7
|
+
export type { DynamicValue } from './resolve-value';
|
|
8
|
+
export type { Animation, AnimationOptions, SingleTweenOptions, KeyframeAnimationOptions, Keyframe, } from './animation';
|
|
9
|
+
export type { TimelineLayer, Timeline } from './timeline';
|
|
10
|
+
export type { RgbaTuple, LerpRgba } from './color';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { DynamicValue } from './resolve-value';
|
|
2
|
+
export type InterpolationStatus = "active" | "inactive" | "dead";
|
|
3
|
+
export type Interpolation = {
|
|
4
|
+
start: () => void;
|
|
5
|
+
stop: () => void;
|
|
6
|
+
kill: () => void;
|
|
7
|
+
setCurrentValue: (value: number) => void;
|
|
8
|
+
currentValue: number;
|
|
9
|
+
velocity: number;
|
|
10
|
+
status: InterpolationStatus;
|
|
11
|
+
};
|
|
12
|
+
export type LerpOptions = {
|
|
13
|
+
to: () => number;
|
|
14
|
+
smoothTimeMs: DynamicValue;
|
|
15
|
+
precision?: number;
|
|
16
|
+
onUpdate?: (value: number, velocity: number) => void;
|
|
17
|
+
onEnded?: () => void;
|
|
18
|
+
};
|
|
19
|
+
export type SmoothDampOptions = {
|
|
20
|
+
to: () => number;
|
|
21
|
+
smoothTimeMs: DynamicValue;
|
|
22
|
+
maxSpeed?: DynamicValue;
|
|
23
|
+
precision?: number;
|
|
24
|
+
onUpdate?: (value: number, velocity: number) => void;
|
|
25
|
+
onEnded?: () => void;
|
|
26
|
+
};
|
|
27
|
+
export type SpringOptions = {
|
|
28
|
+
to: () => number;
|
|
29
|
+
stiffness?: DynamicValue;
|
|
30
|
+
damping?: DynamicValue;
|
|
31
|
+
mass?: DynamicValue;
|
|
32
|
+
precision?: number;
|
|
33
|
+
onUpdate?: (value: number, velocity: number) => void;
|
|
34
|
+
onEnded?: () => void;
|
|
35
|
+
};
|