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
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { DynamicValue } from '../domain';
|
|
2
|
+
import { KeyframeAnimationOptions, AnimationStatus } from './animation';
|
|
3
|
+
export type TimelineLayer = {
|
|
4
|
+
animation: KeyframeAnimationOptions;
|
|
5
|
+
at: DynamicValue;
|
|
6
|
+
} | {
|
|
7
|
+
animation: KeyframeAnimationOptions;
|
|
8
|
+
gap: number;
|
|
9
|
+
};
|
|
10
|
+
export type Timeline = {
|
|
11
|
+
play: () => Promise<void>;
|
|
12
|
+
pause: () => void;
|
|
13
|
+
resume: () => void;
|
|
14
|
+
stop: () => void;
|
|
15
|
+
skipToEnd: () => void;
|
|
16
|
+
kill: () => void;
|
|
17
|
+
setProgress: (value: number) => void;
|
|
18
|
+
progress: number;
|
|
19
|
+
status: AnimationStatus;
|
|
20
|
+
durationMs: number;
|
|
21
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
1
|
-
export { createAnimation } from './animation
|
|
2
|
-
export { createTimeline } from './timeline
|
|
3
|
-
export {
|
|
4
|
-
export { createSmoothDamp } from './smooth-damp
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
8
|
-
export { getTicker } from './ticker
|
|
9
|
-
export {
|
|
10
|
-
export type { Keyframe, AnimationOptions, KeyframeAnimationOptions, } from './
|
|
11
|
-
export type { Animation, Interpolation, EaseName, AnimationStatus, InterpolationStatus, DynamicValue, } from './shared/types';
|
|
12
|
-
export type { SpringOptions } from './spring/create-spring';
|
|
13
|
-
export type { SmoothDampOptions } from './smooth-damp/create-smooth-damp';
|
|
14
|
-
export type { LerpOptions } from './lerp/create-lerp';
|
|
15
|
-
export type { TimelineLayer, Timeline } from './timeline/create-timeline';
|
|
16
|
-
export type { RgbaTuple } from './color/lerp-oklab';
|
|
17
|
-
export type { TickerControls } from './ticker/ticker';
|
|
1
|
+
export { createAnimation } from './animation';
|
|
2
|
+
export { createTimeline } from './timeline';
|
|
3
|
+
export { createLerp } from './lerp';
|
|
4
|
+
export { createSmoothDamp } from './smooth-damp';
|
|
5
|
+
export { createSmoothClamp } from './smooth-clamp';
|
|
6
|
+
export { createSpring } from './spring';
|
|
7
|
+
export { lerpRgba, hexToRgba } from './lerp-rgba';
|
|
8
|
+
export { getTicker } from './ticker';
|
|
9
|
+
export { cubicBezier } from './domain';
|
|
10
|
+
export type { EaseName, Interpolation, AnimationStatus, InterpolationStatus, DynamicValue, Animation, Keyframe, AnimationOptions, KeyframeAnimationOptions, TimelineLayer, Timeline, Ticker, LerpOptions, SmoothDampOptions, SpringOptions, RgbaTuple, } from './domain';
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { cubicBezier } from "./domain/easing.js";
|
|
2
2
|
import { getTicker } from "./ticker/get-ticker.js";
|
|
3
3
|
import { createAnimation } from "./animation/create-animation.js";
|
|
4
4
|
import { createTimeline } from "./timeline/create-timeline.js";
|
|
5
|
-
import { createSpring } from "./spring/create-spring.js";
|
|
6
|
-
import { createSmoothDamp } from "./smooth-damp/create-smooth-damp.js";
|
|
7
5
|
import { createLerp } from "./lerp/create-lerp.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
6
|
+
import { createSmoothDamp } from "./smooth-damp/create-smooth-damp.js";
|
|
7
|
+
import { createSmoothClamp } from "./smooth-clamp/create-smooth-clamp.js";
|
|
8
|
+
import { createSpring } from "./spring/create-spring.js";
|
|
9
|
+
import { lerpRgba } from "./lerp-rgba/lerp-rgba.js";
|
|
10
|
+
import { hexToRgba } from "./lerp-rgba/hex-to-rgba.js";
|
|
11
|
+
export { createAnimation, createLerp, createSmoothClamp, createSmoothDamp, createSpring, createTimeline, cubicBezier, getTicker, hexToRgba, lerpRgba };
|
|
@@ -1,9 +1,2 @@
|
|
|
1
|
-
import { Interpolation,
|
|
2
|
-
export type LerpOptions = {
|
|
3
|
-
to: () => number;
|
|
4
|
-
smoothTimeMs: DynamicValue;
|
|
5
|
-
precision?: number;
|
|
6
|
-
onUpdate?: (value: number, velocity: number) => void;
|
|
7
|
-
onEnded?: () => void;
|
|
8
|
-
};
|
|
1
|
+
import { Interpolation, LerpOptions } from '../domain';
|
|
9
2
|
export declare const createLerp: (options: LerpOptions) => Interpolation;
|
package/dist/lerp/create-lerp.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveValue } from "../domain/resolve-value.js";
|
|
1
2
|
import { getTicker } from "../ticker/get-ticker.js";
|
|
2
3
|
import { lerpStep } from "./step.js";
|
|
3
4
|
//#region src/lerp/create-lerp.ts
|
|
@@ -10,9 +11,22 @@ var createLerp = (options) => {
|
|
|
10
11
|
let currentVelocity = 0;
|
|
11
12
|
let active = true;
|
|
12
13
|
const ticker = getTicker();
|
|
13
|
-
const resolveValue = (v) => typeof v === "function" ? v() : v;
|
|
14
14
|
state.current = options.to();
|
|
15
15
|
previousValue = state.current;
|
|
16
|
+
const update = (deltaMs) => {
|
|
17
|
+
if (!active) return;
|
|
18
|
+
const target = options.to();
|
|
19
|
+
const smoothTimeMs = resolveValue(options.smoothTimeMs);
|
|
20
|
+
lerpStep(state, target, smoothTimeMs, deltaMs);
|
|
21
|
+
currentVelocity = (state.current - previousValue) / (deltaMs / 1e3);
|
|
22
|
+
previousValue = state.current;
|
|
23
|
+
onUpdate?.(state.current, currentVelocity);
|
|
24
|
+
if (onEnded && Math.abs(state.current - target) < precision && Math.abs(currentVelocity) < precision) {
|
|
25
|
+
state.current = target;
|
|
26
|
+
currentVelocity = 0;
|
|
27
|
+
onEnded();
|
|
28
|
+
}
|
|
29
|
+
};
|
|
16
30
|
ticker.add(update);
|
|
17
31
|
const start = () => {
|
|
18
32
|
if (active) return;
|
|
@@ -27,20 +41,6 @@ var createLerp = (options) => {
|
|
|
27
41
|
active = false;
|
|
28
42
|
ticker.remove(update);
|
|
29
43
|
};
|
|
30
|
-
function update(deltaMs) {
|
|
31
|
-
if (!active) return;
|
|
32
|
-
const target = options.to();
|
|
33
|
-
const smoothTimeMs = resolveValue(options.smoothTimeMs);
|
|
34
|
-
lerpStep(state, target, smoothTimeMs, deltaMs);
|
|
35
|
-
currentVelocity = (state.current - previousValue) / (deltaMs / 1e3);
|
|
36
|
-
previousValue = state.current;
|
|
37
|
-
onUpdate?.(state.current, currentVelocity);
|
|
38
|
-
if (onEnded && Math.abs(state.current - target) < precision && Math.abs(currentVelocity) < precision) {
|
|
39
|
-
state.current = target;
|
|
40
|
-
currentVelocity = 0;
|
|
41
|
-
onEnded();
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
44
|
return {
|
|
45
45
|
start,
|
|
46
46
|
stop,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createLerp } from './create-lerp';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a hex color string into an RGBA tuple.
|
|
3
|
+
*
|
|
4
|
+
* Accepts formats:
|
|
5
|
+
* - `#RGB`
|
|
6
|
+
* - `#RGBA`
|
|
7
|
+
* - `#RRGGBB`
|
|
8
|
+
* - `#RRGGBBAA`
|
|
9
|
+
*
|
|
10
|
+
* @param hex - Hex color string with optional leading `#`.
|
|
11
|
+
* @returns An RGBA tuple [R, G, B, A], each 0–1.
|
|
12
|
+
*/
|
|
13
|
+
export declare const hexToRgba: (hex: string) => [number, number, number, number];
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//#region src/lerp-rgba/hex-to-rgba.ts
|
|
2
|
+
/**
|
|
3
|
+
* Parse a hex color string into an RGBA tuple.
|
|
4
|
+
*
|
|
5
|
+
* Accepts formats:
|
|
6
|
+
* - `#RGB`
|
|
7
|
+
* - `#RGBA`
|
|
8
|
+
* - `#RRGGBB`
|
|
9
|
+
* - `#RRGGBBAA`
|
|
10
|
+
*
|
|
11
|
+
* @param hex - Hex color string with optional leading `#`.
|
|
12
|
+
* @returns An RGBA tuple [R, G, B, A], each 0–1.
|
|
13
|
+
*/
|
|
14
|
+
var hexToRgba = (hex) => {
|
|
15
|
+
const cleaned = hex.startsWith("#") ? hex.slice(1) : hex;
|
|
16
|
+
if (!/^[0-9a-fA-F]+$/.test(cleaned)) return [
|
|
17
|
+
0,
|
|
18
|
+
0,
|
|
19
|
+
0,
|
|
20
|
+
1
|
|
21
|
+
];
|
|
22
|
+
if (cleaned.length === 3) return [
|
|
23
|
+
parseInt(cleaned[0] + cleaned[0], 16) / 255,
|
|
24
|
+
parseInt(cleaned[1] + cleaned[1], 16) / 255,
|
|
25
|
+
parseInt(cleaned[2] + cleaned[2], 16) / 255,
|
|
26
|
+
1
|
|
27
|
+
];
|
|
28
|
+
if (cleaned.length === 4) return [
|
|
29
|
+
parseInt(cleaned[0] + cleaned[0], 16) / 255,
|
|
30
|
+
parseInt(cleaned[1] + cleaned[1], 16) / 255,
|
|
31
|
+
parseInt(cleaned[2] + cleaned[2], 16) / 255,
|
|
32
|
+
parseInt(cleaned[3] + cleaned[3], 16) / 255
|
|
33
|
+
];
|
|
34
|
+
if (cleaned.length >= 6) return [
|
|
35
|
+
parseInt(cleaned.slice(0, 2), 16) / 255,
|
|
36
|
+
parseInt(cleaned.slice(2, 4), 16) / 255,
|
|
37
|
+
parseInt(cleaned.slice(4, 6), 16) / 255,
|
|
38
|
+
cleaned.length >= 8 ? parseInt(cleaned.slice(6, 8), 16) / 255 : 1
|
|
39
|
+
];
|
|
40
|
+
return [
|
|
41
|
+
0,
|
|
42
|
+
0,
|
|
43
|
+
0,
|
|
44
|
+
1
|
|
45
|
+
];
|
|
46
|
+
};
|
|
47
|
+
//#endregion
|
|
48
|
+
export { hexToRgba };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { LerpRgba } from '../domain';
|
|
2
|
+
/**
|
|
3
|
+
* Interpolate between two RGBA colors in Oklab space.
|
|
4
|
+
*
|
|
5
|
+
* The RGB channels are converted to Oklab, lerped perceptually uniformly,
|
|
6
|
+
* and converted back. The alpha channel is lerped linearly.
|
|
7
|
+
*
|
|
8
|
+
* @param from - Starting RGBA tuple [R, G, B, A], each 0–1.
|
|
9
|
+
* @param to - Ending RGBA tuple [R, G, B, A], each 0–1.
|
|
10
|
+
* @param progress - Interpolation factor (0 = from, 1 = to).
|
|
11
|
+
* @returns A new RGBA tuple [R, G, B, A], each 0–1.
|
|
12
|
+
*/
|
|
13
|
+
export declare const lerpRgba: LerpRgba;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region src/
|
|
1
|
+
//#region src/lerp-rgba/lerp-rgba.ts
|
|
2
2
|
/**
|
|
3
3
|
* Pre-computed constants for Oklab conversion.
|
|
4
4
|
*
|
|
@@ -121,7 +121,7 @@ var clamp = (value) => {
|
|
|
121
121
|
* @param progress - Interpolation factor (0 = from, 1 = to).
|
|
122
122
|
* @returns A new RGBA tuple [R, G, B, A], each 0–1.
|
|
123
123
|
*/
|
|
124
|
-
var
|
|
124
|
+
var lerpRgba = (from, to, progress) => {
|
|
125
125
|
const clampedProgress = clamp(progress);
|
|
126
126
|
const [lFrom, aFrom, bFrom] = rgbToOklab(from[0], from[1], from[2]);
|
|
127
127
|
const [lTo, aTo, bTo] = rgbToOklab(to[0], to[1], to[2]);
|
|
@@ -133,50 +133,5 @@ var lerpOklab = (from, to, progress) => {
|
|
|
133
133
|
clamp(from[3] + (to[3] - from[3]) * clampedProgress)
|
|
134
134
|
];
|
|
135
135
|
};
|
|
136
|
-
/**
|
|
137
|
-
* Parse a hex color string into an RGBA tuple.
|
|
138
|
-
*
|
|
139
|
-
* Accepts formats:
|
|
140
|
-
* - `#RGB`
|
|
141
|
-
* - `#RGBA`
|
|
142
|
-
* - `#RRGGBB`
|
|
143
|
-
* - `#RRGGBBAA`
|
|
144
|
-
*
|
|
145
|
-
* @param hex - Hex color string with optional leading `#`.
|
|
146
|
-
* @returns An RGBA tuple [R, G, B, A], each 0–1.
|
|
147
|
-
*/
|
|
148
|
-
var hexToRgba = (hex) => {
|
|
149
|
-
const cleaned = hex.startsWith("#") ? hex.slice(1) : hex;
|
|
150
|
-
if (!/^[0-9a-fA-F]+$/.test(cleaned)) return [
|
|
151
|
-
0,
|
|
152
|
-
0,
|
|
153
|
-
0,
|
|
154
|
-
1
|
|
155
|
-
];
|
|
156
|
-
if (cleaned.length === 3) return [
|
|
157
|
-
parseInt(cleaned[0] + cleaned[0], 16) / 255,
|
|
158
|
-
parseInt(cleaned[1] + cleaned[1], 16) / 255,
|
|
159
|
-
parseInt(cleaned[2] + cleaned[2], 16) / 255,
|
|
160
|
-
1
|
|
161
|
-
];
|
|
162
|
-
if (cleaned.length === 4) return [
|
|
163
|
-
parseInt(cleaned[0] + cleaned[0], 16) / 255,
|
|
164
|
-
parseInt(cleaned[1] + cleaned[1], 16) / 255,
|
|
165
|
-
parseInt(cleaned[2] + cleaned[2], 16) / 255,
|
|
166
|
-
parseInt(cleaned[3] + cleaned[3], 16) / 255
|
|
167
|
-
];
|
|
168
|
-
if (cleaned.length >= 6) return [
|
|
169
|
-
parseInt(cleaned.slice(0, 2), 16) / 255,
|
|
170
|
-
parseInt(cleaned.slice(2, 4), 16) / 255,
|
|
171
|
-
parseInt(cleaned.slice(4, 6), 16) / 255,
|
|
172
|
-
cleaned.length >= 8 ? parseInt(cleaned.slice(6, 8), 16) / 255 : 1
|
|
173
|
-
];
|
|
174
|
-
return [
|
|
175
|
-
0,
|
|
176
|
-
0,
|
|
177
|
-
0,
|
|
178
|
-
1
|
|
179
|
-
];
|
|
180
|
-
};
|
|
181
136
|
//#endregion
|
|
182
|
-
export {
|
|
137
|
+
export { lerpRgba };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createSmoothClamp } from './create-smooth-clamp';
|
|
@@ -1,10 +1,2 @@
|
|
|
1
|
-
import { Interpolation,
|
|
2
|
-
export type SmoothDampOptions = {
|
|
3
|
-
to: () => number;
|
|
4
|
-
smoothTimeMs: DynamicValue;
|
|
5
|
-
maxSpeed?: DynamicValue;
|
|
6
|
-
precision?: number;
|
|
7
|
-
onUpdate?: (value: number, velocity: number) => void;
|
|
8
|
-
onEnded?: () => void;
|
|
9
|
-
};
|
|
1
|
+
import { Interpolation, SmoothDampOptions } from '../domain';
|
|
10
2
|
export declare const createSmoothDamp: (options: SmoothDampOptions) => Interpolation;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveValue } from "../domain/resolve-value.js";
|
|
1
2
|
import { getTicker } from "../ticker/get-ticker.js";
|
|
2
3
|
import { smoothDampStep } from "./step.js";
|
|
3
4
|
//#region src/smooth-damp/create-smooth-damp.ts
|
|
@@ -11,7 +12,6 @@ var createSmoothDamp = (options) => {
|
|
|
11
12
|
};
|
|
12
13
|
let active = true;
|
|
13
14
|
const ticker = getTicker();
|
|
14
|
-
const resolveValue = (v) => typeof v === "function" ? v() : v;
|
|
15
15
|
state.current = options.to();
|
|
16
16
|
ticker.add(update);
|
|
17
17
|
const start = () => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createSmoothDamp } from './create-smooth-damp';
|
|
@@ -1,11 +1,2 @@
|
|
|
1
|
-
import { Interpolation,
|
|
2
|
-
export type SpringOptions = {
|
|
3
|
-
to: () => number;
|
|
4
|
-
stiffness?: DynamicValue;
|
|
5
|
-
damping?: DynamicValue;
|
|
6
|
-
mass?: DynamicValue;
|
|
7
|
-
precision?: number;
|
|
8
|
-
onUpdate?: (value: number, velocity: number) => void;
|
|
9
|
-
onEnded?: () => void;
|
|
10
|
-
};
|
|
1
|
+
import { Interpolation, SpringOptions } from '../domain';
|
|
11
2
|
export declare const createSpring: (options: SpringOptions) => Interpolation;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveValue } from "../domain/resolve-value.js";
|
|
1
2
|
import { getTicker } from "../ticker/get-ticker.js";
|
|
2
3
|
import { verletStep } from "./verlet.js";
|
|
3
4
|
//#region src/spring/create-spring.ts
|
|
@@ -5,7 +6,6 @@ var createSpring = (options) => {
|
|
|
5
6
|
const precision = options.precision ?? .01;
|
|
6
7
|
const onUpdate = options.onUpdate;
|
|
7
8
|
const onEnded = options.onEnded;
|
|
8
|
-
const resolveValue = (v) => typeof v === "function" ? v() : v;
|
|
9
9
|
const rawTo = options.to;
|
|
10
10
|
const rawStiffness = options.stiffness ?? 180;
|
|
11
11
|
const rawDamping = options.damping ?? 12;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createSpring } from './create-spring';
|
|
@@ -1,5 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Ticker } from '../domain';
|
|
2
2
|
/** Returns the default ticker singleton. Created lazily on first access. */
|
|
3
|
-
export declare const getTicker: () =>
|
|
4
|
-
/**
|
|
5
|
-
|
|
3
|
+
export declare const getTicker: () => Ticker;
|
|
4
|
+
/**
|
|
5
|
+
* Create a ticker that drives active animations.
|
|
6
|
+
*
|
|
7
|
+
* Does NOT auto-start. The user must explicitly call either:
|
|
8
|
+
* - `start()` — begins a `requestAnimationFrame` loop
|
|
9
|
+
* - `update(deltaMs)` — drive manually from a game loop
|
|
10
|
+
*
|
|
11
|
+
* `add()` and `remove()` register/unregister animations without
|
|
12
|
+
* side effects on the rAF loop.
|
|
13
|
+
*
|
|
14
|
+
* Uses a flat array with undefined-tombstone removal for safe concurrent
|
|
15
|
+
* modification during iteration. Compacted after each frame.
|
|
16
|
+
*/
|
|
17
|
+
export declare const createTicker: () => Ticker;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { createTicker } from "./ticker.js";
|
|
2
1
|
//#region src/ticker/get-ticker.ts
|
|
3
2
|
var singleton;
|
|
4
3
|
/** Returns the default ticker singleton. Created lazily on first access. */
|
|
@@ -6,5 +5,76 @@ var getTicker = () => {
|
|
|
6
5
|
if (!singleton) singleton = createTicker();
|
|
7
6
|
return singleton;
|
|
8
7
|
};
|
|
8
|
+
/**
|
|
9
|
+
* Create a ticker that drives active animations.
|
|
10
|
+
*
|
|
11
|
+
* Does NOT auto-start. The user must explicitly call either:
|
|
12
|
+
* - `start()` — begins a `requestAnimationFrame` loop
|
|
13
|
+
* - `update(deltaMs)` — drive manually from a game loop
|
|
14
|
+
*
|
|
15
|
+
* `add()` and `remove()` register/unregister animations without
|
|
16
|
+
* side effects on the rAF loop.
|
|
17
|
+
*
|
|
18
|
+
* Uses a flat array with undefined-tombstone removal for safe concurrent
|
|
19
|
+
* modification during iteration. Compacted after each frame.
|
|
20
|
+
*/
|
|
21
|
+
var createTicker = () => {
|
|
22
|
+
const activeAnimations = [];
|
|
23
|
+
let needsCompact = false;
|
|
24
|
+
let animationFrameRequestId = void 0;
|
|
25
|
+
let previousFrameTime = void 0;
|
|
26
|
+
const start = () => {
|
|
27
|
+
if (animationFrameRequestId !== void 0) return;
|
|
28
|
+
previousFrameTime = void 0;
|
|
29
|
+
const tick = (now) => {
|
|
30
|
+
if (previousFrameTime !== void 0) {
|
|
31
|
+
const delta = now - previousFrameTime;
|
|
32
|
+
update(delta);
|
|
33
|
+
}
|
|
34
|
+
previousFrameTime = now;
|
|
35
|
+
animationFrameRequestId = requestAnimationFrame(tick);
|
|
36
|
+
};
|
|
37
|
+
animationFrameRequestId = requestAnimationFrame(tick);
|
|
38
|
+
};
|
|
39
|
+
const stop = () => {
|
|
40
|
+
if (animationFrameRequestId !== void 0) {
|
|
41
|
+
cancelAnimationFrame(animationFrameRequestId);
|
|
42
|
+
animationFrameRequestId = void 0;
|
|
43
|
+
}
|
|
44
|
+
previousFrameTime = void 0;
|
|
45
|
+
};
|
|
46
|
+
const update = (deltaMs) => {
|
|
47
|
+
for (let i = 0; i < activeAnimations.length; i++) {
|
|
48
|
+
const anim = activeAnimations[i];
|
|
49
|
+
if (anim) anim(deltaMs);
|
|
50
|
+
}
|
|
51
|
+
if (needsCompact) {
|
|
52
|
+
let writeIndex = 0;
|
|
53
|
+
for (let readIndex = 0; readIndex < activeAnimations.length; readIndex++) {
|
|
54
|
+
const anim = activeAnimations[readIndex];
|
|
55
|
+
if (anim !== void 0) activeAnimations[writeIndex++] = anim;
|
|
56
|
+
}
|
|
57
|
+
activeAnimations.length = writeIndex;
|
|
58
|
+
needsCompact = false;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const add = (handler) => {
|
|
62
|
+
activeAnimations.push(handler);
|
|
63
|
+
};
|
|
64
|
+
const remove = (handler) => {
|
|
65
|
+
const index = activeAnimations.indexOf(handler);
|
|
66
|
+
if (index >= 0) {
|
|
67
|
+
activeAnimations[index] = void 0;
|
|
68
|
+
needsCompact = true;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
start,
|
|
73
|
+
stop,
|
|
74
|
+
update,
|
|
75
|
+
add,
|
|
76
|
+
remove
|
|
77
|
+
};
|
|
78
|
+
};
|
|
9
79
|
//#endregion
|
|
10
|
-
export { getTicker };
|
|
80
|
+
export { createTicker, getTicker };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { getTicker } from './get-ticker';
|
|
@@ -1,24 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { AnimationStatus, DynamicValue } from '../shared/types';
|
|
3
|
-
export type TimelineLayer = {
|
|
4
|
-
animation: KeyframeAnimationOptions;
|
|
5
|
-
at: DynamicValue;
|
|
6
|
-
} | {
|
|
7
|
-
animation: KeyframeAnimationOptions;
|
|
8
|
-
gap: number;
|
|
9
|
-
};
|
|
10
|
-
export type Timeline = {
|
|
11
|
-
play: () => Promise<Timeline>;
|
|
12
|
-
pause: () => void;
|
|
13
|
-
resume: () => void;
|
|
14
|
-
stop: () => void;
|
|
15
|
-
skipToEnd: () => void;
|
|
16
|
-
kill: () => void;
|
|
17
|
-
setProgress: (value: number) => void;
|
|
18
|
-
progress: number;
|
|
19
|
-
status: AnimationStatus;
|
|
20
|
-
durationMs: number;
|
|
21
|
-
};
|
|
1
|
+
import { TimelineLayer, Timeline } from '../domain';
|
|
22
2
|
export declare const createTimeline: (layers: TimelineLayer[], options?: {
|
|
23
3
|
onStarted?: () => void;
|
|
24
4
|
onProgress?: (progress: number) => void;
|
|
@@ -1,10 +1,9 @@
|
|
|
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 } from "../animation/runner.js";
|
|
4
5
|
//#region src/timeline/create-timeline.ts
|
|
5
|
-
var
|
|
6
|
-
var resolveValue = (v) => typeof v === "function" ? v() : v;
|
|
7
|
-
var resolveEasing = (ease) => typeof ease === "function" ? ease : easingFunctions[ease];
|
|
6
|
+
var noOp = () => {};
|
|
8
7
|
var buildFromConfigs = (rawLayers) => {
|
|
9
8
|
const activeLayers = [];
|
|
10
9
|
let previousEndAt = 0;
|
|
@@ -40,8 +39,7 @@ var buildFromConfigs = (rawLayers) => {
|
|
|
40
39
|
};
|
|
41
40
|
};
|
|
42
41
|
var createTimeline = (layers, options) => {
|
|
43
|
-
const { onStarted, onEnded } = options ?? {};
|
|
44
|
-
const onProgress = options?.onProgress ?? noop;
|
|
42
|
+
const { onStarted, onEnded, onProgress = noOp } = options ?? {};
|
|
45
43
|
const rawLayers = layers;
|
|
46
44
|
let state = buildFromConfigs(rawLayers);
|
|
47
45
|
let activeLayers = state.activeLayers;
|
|
@@ -55,7 +53,7 @@ var createTimeline = (layers, options) => {
|
|
|
55
53
|
timelineStatus = "stopped";
|
|
56
54
|
ticker.remove(update);
|
|
57
55
|
onEnded?.();
|
|
58
|
-
resolvePromise?.(
|
|
56
|
+
resolvePromise?.();
|
|
59
57
|
resolvePromise = void 0;
|
|
60
58
|
};
|
|
61
59
|
const update = (deltaMs) => {
|
|
@@ -105,7 +103,7 @@ var createTimeline = (layers, options) => {
|
|
|
105
103
|
if (timelineStatus !== "playing" && timelineStatus !== "paused") return;
|
|
106
104
|
timelineStatus = "stopped";
|
|
107
105
|
ticker.remove(update);
|
|
108
|
-
resolvePromise?.(
|
|
106
|
+
resolvePromise?.();
|
|
109
107
|
resolvePromise = void 0;
|
|
110
108
|
};
|
|
111
109
|
const skipToEnd = () => {
|
|
@@ -116,7 +114,7 @@ var createTimeline = (layers, options) => {
|
|
|
116
114
|
timelineStatus = "stopped";
|
|
117
115
|
ticker.remove(update);
|
|
118
116
|
onEnded?.();
|
|
119
|
-
resolvePromise?.(
|
|
117
|
+
resolvePromise?.();
|
|
120
118
|
resolvePromise = void 0;
|
|
121
119
|
};
|
|
122
120
|
const kill = () => {
|
|
@@ -140,7 +138,7 @@ var createTimeline = (layers, options) => {
|
|
|
140
138
|
}
|
|
141
139
|
remainingLayers = activeLayers.filter((l) => !l.ended).length;
|
|
142
140
|
};
|
|
143
|
-
|
|
141
|
+
return {
|
|
144
142
|
play,
|
|
145
143
|
pause,
|
|
146
144
|
resume,
|
|
@@ -158,7 +156,6 @@ var createTimeline = (layers, options) => {
|
|
|
158
156
|
return totalDurationMs;
|
|
159
157
|
}
|
|
160
158
|
};
|
|
161
|
-
return timeline;
|
|
162
159
|
};
|
|
163
160
|
//#endregion
|
|
164
161
|
export { createTimeline };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createTimeline } from './create-timeline';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anim-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "JavaScript library for animating numbers",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"build": "npm run lint && npm run test && tsc && vite build",
|
|
23
23
|
"preview": "vite preview",
|
|
24
24
|
"check:types": "tsc --noEmit",
|
|
25
|
-
"lint": "oxlint
|
|
25
|
+
"lint": "oxlint",
|
|
26
26
|
"test": "vitest run",
|
|
27
27
|
"test:watch": "vitest",
|
|
28
28
|
"storybook": "storybook dev -p 6006",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"gsap": "3.15.0",
|
|
43
43
|
"oxfmt": "0.57.0",
|
|
44
44
|
"oxlint": "1.72.0",
|
|
45
|
+
"oxlint-tsgolint": "^0.24.0",
|
|
45
46
|
"storybook": "10.4.6",
|
|
46
47
|
"typescript": "6.0.3",
|
|
47
48
|
"vite": "8.1.3",
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
export type RgbaTuple = readonly [number, number, number, number];
|
|
2
|
-
/**
|
|
3
|
-
* Interpolate between two RGBA colors in Oklab space.
|
|
4
|
-
*
|
|
5
|
-
* The RGB channels are converted to Oklab, lerped perceptually uniformly,
|
|
6
|
-
* and converted back. The alpha channel is lerped linearly.
|
|
7
|
-
*
|
|
8
|
-
* @param from - Starting RGBA tuple [R, G, B, A], each 0–1.
|
|
9
|
-
* @param to - Ending RGBA tuple [R, G, B, A], each 0–1.
|
|
10
|
-
* @param progress - Interpolation factor (0 = from, 1 = to).
|
|
11
|
-
* @returns A new RGBA tuple [R, G, B, A], each 0–1.
|
|
12
|
-
*/
|
|
13
|
-
export declare const lerpOklab: (from: RgbaTuple, to: RgbaTuple, progress: number) => [number, number, number, number];
|
|
14
|
-
/**
|
|
15
|
-
* Parse a hex color string into an RGBA tuple.
|
|
16
|
-
*
|
|
17
|
-
* Accepts formats:
|
|
18
|
-
* - `#RGB`
|
|
19
|
-
* - `#RGBA`
|
|
20
|
-
* - `#RRGGBB`
|
|
21
|
-
* - `#RRGGBBAA`
|
|
22
|
-
*
|
|
23
|
-
* @param hex - Hex color string with optional leading `#`.
|
|
24
|
-
* @returns An RGBA tuple [R, G, B, A], each 0–1.
|
|
25
|
-
*/
|
|
26
|
-
export declare const hexToRgba: (hex: string) => [number, number, number, number];
|
package/dist/easing/easing.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { EaseName, EaseFunction } from '../shared/types';
|
|
2
|
-
/** All 31 named easing identifiers, ordered by type. */
|
|
3
|
-
export declare const EASE_NAMES: EaseName[];
|
|
4
|
-
export declare const easingFunctions: Record<EaseName, EaseFunction>;
|
|
5
|
-
/**
|
|
6
|
-
* Cubic bezier easing. Builds a pre-computed lookup table at construction time
|
|
7
|
-
* for O(log n) binary search at runtime — no Newton iteration, no allocation
|
|
8
|
-
* per frame.
|
|
9
|
-
*
|
|
10
|
-
* @param p1x - First control point x coordinate.
|
|
11
|
-
* @param p1y - First control point y coordinate.
|
|
12
|
-
* @param p2x - Second control point x coordinate.
|
|
13
|
-
* @param p2y - Second control point y coordinate.
|
|
14
|
-
* @returns An EaseFunction suitable for use with animate() or createTimeline().
|
|
15
|
-
*/
|
|
16
|
-
export declare const cubicBezier: (p1x: number, p1y: number, p2x: number, p2y: number) => EaseFunction;
|