@freestylejs/ani-core 1.0.0 → 1.2.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/dist/index.cjs +753 -383
- package/dist/index.d.cts +389 -263
- package/dist/index.d.ts +389 -263
- package/dist/index.js +745 -369
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,39 @@
|
|
|
1
|
-
// src/
|
|
1
|
+
// src/utils/time/is_end.ts
|
|
2
|
+
function isEndOfAnimation(currentT, duration, tolerance = 1e-3) {
|
|
3
|
+
return currentT === duration || currentT - duration >= tolerance;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// src/ani/core/engine.ts
|
|
7
|
+
function calculateSegmentState(localTime, segmentDef, dt = 0) {
|
|
8
|
+
const t = Math.max(0, Math.min(localTime, segmentDef.duration));
|
|
9
|
+
const animeValues = [];
|
|
10
|
+
let allComplete = true;
|
|
11
|
+
const isMultipleTiming = Array.isArray(segmentDef.timing);
|
|
12
|
+
if (isMultipleTiming && segmentDef.timing.length !== segmentDef.from.length) {
|
|
13
|
+
throw new TypeError(
|
|
14
|
+
`[calculateSegmentState] timing does not correctly set. It requires multiple timing for ${segmentDef.from}, but received ${segmentDef.timing}`
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
for (let i = 0; i < segmentDef.from.length; i++) {
|
|
18
|
+
const timingFunction = isMultipleTiming ? segmentDef.timing[i] : segmentDef.timing;
|
|
19
|
+
const animeResponse = timingFunction.step(t, {
|
|
20
|
+
dt,
|
|
21
|
+
from: segmentDef.from[i],
|
|
22
|
+
to: segmentDef.to[i],
|
|
23
|
+
duration: segmentDef.duration
|
|
24
|
+
});
|
|
25
|
+
animeValues.push(animeResponse.value);
|
|
26
|
+
if (!animeResponse.endOfAnimation) {
|
|
27
|
+
allComplete = false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
values: animeValues,
|
|
32
|
+
isComplete: allComplete || isEndOfAnimation(t, segmentDef.duration)
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/nodes/base.ts
|
|
2
37
|
var AnimationNode = class {
|
|
3
38
|
constructor(id) {
|
|
4
39
|
if (id) {
|
|
@@ -28,6 +63,113 @@ var TimingFunction = class _TimingFunction {
|
|
|
28
63
|
}
|
|
29
64
|
};
|
|
30
65
|
|
|
66
|
+
// src/timing/bezier.ts
|
|
67
|
+
var NEWTON_ITERATIONS = 4;
|
|
68
|
+
var NEWTON_MIN_SLOPE = 1e-3;
|
|
69
|
+
var SUBDIVISION_PRECISION = 1e-7;
|
|
70
|
+
var SUBDIVISION_MAX_ITERATIONS = 10;
|
|
71
|
+
var SAMPLE_TABLE_SIZE = 11;
|
|
72
|
+
var SAMPLE_STEP_SIZE = 1 / (SAMPLE_TABLE_SIZE - 1);
|
|
73
|
+
var BezierTimingFunction = class extends TimingFunction {
|
|
74
|
+
constructor(opt) {
|
|
75
|
+
super();
|
|
76
|
+
this.opt = opt;
|
|
77
|
+
this.sampleValues = null;
|
|
78
|
+
if (this.opt.p2.x !== this.opt.p2.y || this.opt.p3.x !== this.opt.p3.y) {
|
|
79
|
+
this.sampleValues = new Float32Array(SAMPLE_TABLE_SIZE);
|
|
80
|
+
for (let i = 0; i < SAMPLE_TABLE_SIZE; ++i) {
|
|
81
|
+
this.sampleValues[i] = this.calcBezier(
|
|
82
|
+
i * SAMPLE_STEP_SIZE,
|
|
83
|
+
this.opt.p2.x,
|
|
84
|
+
this.opt.p3.x
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
calcBezier(t, a1, a2) {
|
|
90
|
+
return ((1 - 3 * a2 + 3 * a1) * t + (3 * a2 - 6 * a1)) * t * t + 3 * a1 * t;
|
|
91
|
+
}
|
|
92
|
+
getSlope(t, a1, a2) {
|
|
93
|
+
return 3 * (1 - 3 * a2 + 3 * a1) * t * t + 2 * (3 * a2 - 6 * a1) * t + 3 * a1;
|
|
94
|
+
}
|
|
95
|
+
getTForX(x) {
|
|
96
|
+
const mX1 = this.opt.p2.x;
|
|
97
|
+
const mX2 = this.opt.p3.x;
|
|
98
|
+
let intervalStart = 0;
|
|
99
|
+
let currentSample = 1;
|
|
100
|
+
const lastSample = SAMPLE_TABLE_SIZE - 1;
|
|
101
|
+
for (; currentSample !== lastSample && this.sampleValues[currentSample] <= x; ++currentSample) {
|
|
102
|
+
intervalStart += SAMPLE_STEP_SIZE;
|
|
103
|
+
}
|
|
104
|
+
--currentSample;
|
|
105
|
+
const dist = (x - this.sampleValues[currentSample]) / (this.sampleValues[currentSample + 1] - this.sampleValues[currentSample]);
|
|
106
|
+
const guessForT = intervalStart + dist * SAMPLE_STEP_SIZE;
|
|
107
|
+
const initialSlope = this.getSlope(guessForT, mX1, mX2);
|
|
108
|
+
if (initialSlope >= NEWTON_MIN_SLOPE) {
|
|
109
|
+
return this.newtonRaphsonIterate(x, guessForT, mX1, mX2);
|
|
110
|
+
}
|
|
111
|
+
if (initialSlope === 0) {
|
|
112
|
+
return guessForT;
|
|
113
|
+
}
|
|
114
|
+
return this.binarySubdivide(
|
|
115
|
+
x,
|
|
116
|
+
intervalStart,
|
|
117
|
+
intervalStart + SAMPLE_STEP_SIZE,
|
|
118
|
+
mX1,
|
|
119
|
+
mX2
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
binarySubdivide(aX, aA, aB, mX1, mX2) {
|
|
123
|
+
let currentX;
|
|
124
|
+
let currentT;
|
|
125
|
+
let i = 0;
|
|
126
|
+
let currentA = aA;
|
|
127
|
+
let currentB = aB;
|
|
128
|
+
do {
|
|
129
|
+
currentT = currentA + (currentB - currentA) / 2;
|
|
130
|
+
currentX = this.calcBezier(currentT, mX1, mX2) - aX;
|
|
131
|
+
if (currentX > 0) {
|
|
132
|
+
currentB = currentT;
|
|
133
|
+
} else {
|
|
134
|
+
currentA = currentT;
|
|
135
|
+
}
|
|
136
|
+
} while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS);
|
|
137
|
+
return currentT;
|
|
138
|
+
}
|
|
139
|
+
newtonRaphsonIterate(aX, aGuessT, mX1, mX2) {
|
|
140
|
+
let guessT = aGuessT;
|
|
141
|
+
for (let i = 0; i < NEWTON_ITERATIONS; ++i) {
|
|
142
|
+
const currentSlope = this.getSlope(guessT, mX1, mX2);
|
|
143
|
+
if (currentSlope === 0) {
|
|
144
|
+
return guessT;
|
|
145
|
+
}
|
|
146
|
+
const currentX = this.calcBezier(guessT, mX1, mX2) - aX;
|
|
147
|
+
guessT -= currentX / currentSlope;
|
|
148
|
+
}
|
|
149
|
+
return guessT;
|
|
150
|
+
}
|
|
151
|
+
step(time, context) {
|
|
152
|
+
const { duration, from, to } = context;
|
|
153
|
+
if (duration === 0) {
|
|
154
|
+
return { value: to, endOfAnimation: true };
|
|
155
|
+
}
|
|
156
|
+
const x = Math.max(0, Math.min(time / duration, 1));
|
|
157
|
+
let easedT = x;
|
|
158
|
+
if (this.opt.p2.x !== this.opt.p2.y || this.opt.p3.x !== this.opt.p3.y) {
|
|
159
|
+
if (!this.sampleValues) {
|
|
160
|
+
}
|
|
161
|
+
const t = this.getTForX(x);
|
|
162
|
+
easedT = this.calcBezier(t, this.opt.p2.y, this.opt.p3.y);
|
|
163
|
+
}
|
|
164
|
+
const value = from + (to - from) * easedT;
|
|
165
|
+
const endOfAnimation = time >= duration;
|
|
166
|
+
return {
|
|
167
|
+
value,
|
|
168
|
+
endOfAnimation
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
31
173
|
// src/timing/linear.ts
|
|
32
174
|
var LinearTimingFunction = class extends TimingFunction {
|
|
33
175
|
step(time, context) {
|
|
@@ -37,29 +179,72 @@ var LinearTimingFunction = class extends TimingFunction {
|
|
|
37
179
|
}
|
|
38
180
|
};
|
|
39
181
|
|
|
40
|
-
// src/timing/
|
|
41
|
-
var
|
|
182
|
+
// src/timing/dynamic_spring.ts
|
|
183
|
+
var DynamicSpringTimingFunction = class extends TimingFunction {
|
|
42
184
|
constructor(opt) {
|
|
43
185
|
super();
|
|
44
186
|
this.opt = opt;
|
|
45
|
-
this.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
};
|
|
49
|
-
this.p4 = {
|
|
50
|
-
x: 1,
|
|
51
|
-
y: 1
|
|
52
|
-
};
|
|
187
|
+
this.currentValue = 0;
|
|
188
|
+
this.currentVelocity = 0;
|
|
189
|
+
this.isInitialized = false;
|
|
53
190
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
191
|
+
init(startValue) {
|
|
192
|
+
this.currentValue = startValue;
|
|
193
|
+
this.currentVelocity = 0;
|
|
194
|
+
this.isInitialized = true;
|
|
57
195
|
}
|
|
58
|
-
|
|
59
|
-
const
|
|
196
|
+
getDerivatives(state, to) {
|
|
197
|
+
const { m, k, c } = this.opt;
|
|
198
|
+
const displacement = state.x - to;
|
|
199
|
+
const a2 = (-k * displacement - c * state.v) / m;
|
|
200
|
+
return { dx: state.v, dv: a2 };
|
|
201
|
+
}
|
|
202
|
+
step(_time, context) {
|
|
203
|
+
if (!this.isInitialized) {
|
|
204
|
+
this.init(context.from);
|
|
205
|
+
}
|
|
206
|
+
const { to, tolerance, dt } = context;
|
|
207
|
+
if (dt === 0) {
|
|
208
|
+
return {
|
|
209
|
+
value: this.currentValue,
|
|
210
|
+
endOfAnimation: false
|
|
211
|
+
// Or check for end state
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const x = this.currentValue;
|
|
215
|
+
const v = this.currentVelocity;
|
|
216
|
+
const k1 = this.getDerivatives({ x, v }, to);
|
|
217
|
+
const k2State = {
|
|
218
|
+
x: x + k1.dx * (dt / 2),
|
|
219
|
+
v: v + k1.dv * (dt / 2)
|
|
220
|
+
};
|
|
221
|
+
const k2 = this.getDerivatives(k2State, to);
|
|
222
|
+
const k3State = {
|
|
223
|
+
x: x + k2.dx * (dt / 2),
|
|
224
|
+
v: v + k2.dv * (dt / 2)
|
|
225
|
+
};
|
|
226
|
+
const k3 = this.getDerivatives(k3State, to);
|
|
227
|
+
const k4State = {
|
|
228
|
+
x: x + k3.dx * dt,
|
|
229
|
+
v: v + k3.dv * dt
|
|
230
|
+
};
|
|
231
|
+
const k4 = this.getDerivatives(k4State, to);
|
|
232
|
+
const avgDx = 1 / 6 * (k1.dx + 2 * k2.dx + 2 * k3.dx + k4.dx);
|
|
233
|
+
const avgDv = 1 / 6 * (k1.dv + 2 * k2.dv + 2 * k3.dv + k4.dv);
|
|
234
|
+
this.currentValue = x + avgDx * dt;
|
|
235
|
+
this.currentVelocity = v + avgDv * dt;
|
|
236
|
+
const tol = tolerance ?? 1e-3;
|
|
237
|
+
const displacement = this.currentValue - to;
|
|
238
|
+
const isMoving = Math.abs(this.currentVelocity) > tol;
|
|
239
|
+
const isDisplaced = Math.abs(displacement) > tol;
|
|
240
|
+
const endOfAnimation = !isMoving && !isDisplaced;
|
|
241
|
+
if (endOfAnimation) {
|
|
242
|
+
this.currentValue = to;
|
|
243
|
+
this.currentVelocity = 0;
|
|
244
|
+
}
|
|
60
245
|
return {
|
|
61
|
-
value:
|
|
62
|
-
endOfAnimation
|
|
246
|
+
value: this.currentValue,
|
|
247
|
+
endOfAnimation
|
|
63
248
|
};
|
|
64
249
|
}
|
|
65
250
|
};
|
|
@@ -134,30 +319,62 @@ var T = {
|
|
|
134
319
|
* Creates a new Bezier timing function instance.
|
|
135
320
|
*
|
|
136
321
|
* @param {Bezier.BezierTimingFunctionOpt} opt - Options for configuring the Bezier curve.
|
|
137
|
-
* A new instance of BezierTimingFunction.
|
|
138
322
|
*/
|
|
139
323
|
bezier: (opt) => new BezierTimingFunction(opt),
|
|
140
324
|
/**
|
|
141
325
|
* Creates a new Spring timing function instance.
|
|
142
326
|
*
|
|
143
|
-
* @param {
|
|
144
|
-
* A new instance of SpringTimingFunction.
|
|
327
|
+
* @param {SpringTimingFunctionOpt} opt - Options for configuring the Spring timing function.
|
|
145
328
|
*/
|
|
146
329
|
spring: (opt) => new SpringTimingFunction(opt),
|
|
330
|
+
/**
|
|
331
|
+
* Creates a new Dynamic Spring timing function instance.
|
|
332
|
+
*
|
|
333
|
+
* @param SpringTimingFunctionOpt} opt - Options for configuring the Spring timing function.
|
|
334
|
+
*/
|
|
335
|
+
dynamicSpring: (opt) => new DynamicSpringTimingFunction(opt),
|
|
147
336
|
/**
|
|
148
337
|
* Creates linear timing function instance.
|
|
149
338
|
*/
|
|
150
|
-
linear: () => new LinearTimingFunction()
|
|
339
|
+
linear: () => new LinearTimingFunction(),
|
|
340
|
+
/**
|
|
341
|
+
* Standard CSS 'ease' timing function (0.25, 0.1, 0.25, 1.0).
|
|
342
|
+
*/
|
|
343
|
+
ease: () => new BezierTimingFunction({
|
|
344
|
+
p2: { x: 0.25, y: 0.1 },
|
|
345
|
+
p3: { x: 0.25, y: 1 }
|
|
346
|
+
}),
|
|
347
|
+
/**
|
|
348
|
+
* Standard CSS 'ease-in' timing function (0.42, 0, 1.0, 1.0).
|
|
349
|
+
*/
|
|
350
|
+
easeIn: () => new BezierTimingFunction({
|
|
351
|
+
p2: { x: 0.42, y: 0 },
|
|
352
|
+
p3: { x: 1, y: 1 }
|
|
353
|
+
}),
|
|
354
|
+
/**
|
|
355
|
+
* Standard CSS 'ease-out' timing function (0, 0, 0.58, 1.0).
|
|
356
|
+
*/
|
|
357
|
+
easeOut: () => new BezierTimingFunction({
|
|
358
|
+
p2: { x: 0, y: 0 },
|
|
359
|
+
p3: { x: 0.58, y: 1 }
|
|
360
|
+
}),
|
|
361
|
+
/**
|
|
362
|
+
* Standard CSS 'ease-in-out' timing function (0.42, 0, 0.58, 1.0).
|
|
363
|
+
*/
|
|
364
|
+
easeInOut: () => new BezierTimingFunction({
|
|
365
|
+
p2: { x: 0.42, y: 0 },
|
|
366
|
+
p3: { x: 0.58, y: 1 }
|
|
367
|
+
})
|
|
151
368
|
};
|
|
152
369
|
|
|
153
|
-
// src/
|
|
370
|
+
// src/nodes/segment.ts
|
|
154
371
|
var SegmentNode = class extends AnimationNode {
|
|
155
372
|
constructor(props, id) {
|
|
156
373
|
super(id);
|
|
157
374
|
this.type = "SEGMENT";
|
|
158
375
|
const nodeProps = {
|
|
159
376
|
to: props.to,
|
|
160
|
-
duration: props.duration
|
|
377
|
+
duration: props.duration,
|
|
161
378
|
...props.timing !== void 0 && { timing: props.timing }
|
|
162
379
|
};
|
|
163
380
|
this.props = nodeProps;
|
|
@@ -177,7 +394,7 @@ function ani(props, id) {
|
|
|
177
394
|
return new SegmentNode(props, id);
|
|
178
395
|
}
|
|
179
396
|
|
|
180
|
-
// src/
|
|
397
|
+
// src/nodes/composition.ts
|
|
181
398
|
var CompositionNode = class _CompositionNode extends AnimationNode {
|
|
182
399
|
constructor(children, timing, id) {
|
|
183
400
|
super(id);
|
|
@@ -206,12 +423,12 @@ var CompositionNode = class _CompositionNode extends AnimationNode {
|
|
|
206
423
|
}
|
|
207
424
|
};
|
|
208
425
|
|
|
209
|
-
// src/
|
|
426
|
+
// src/nodes/delay.ts
|
|
210
427
|
function delay(duration, id) {
|
|
211
428
|
return new SegmentNode({ to: {}, duration }, id);
|
|
212
429
|
}
|
|
213
430
|
|
|
214
|
-
// src/
|
|
431
|
+
// src/nodes/loop.ts
|
|
215
432
|
var LoopNode = class extends CompositionNode {
|
|
216
433
|
constructor(child, loopCount, timing, id) {
|
|
217
434
|
super([child], timing, id);
|
|
@@ -232,7 +449,7 @@ function loop(child, loopCount, timing, id) {
|
|
|
232
449
|
return new LoopNode(child, loopCount, timing, id);
|
|
233
450
|
}
|
|
234
451
|
|
|
235
|
-
// src/
|
|
452
|
+
// src/nodes/parallel.ts
|
|
236
453
|
var ParallelNode = class extends CompositionNode {
|
|
237
454
|
constructor(children, timing, id) {
|
|
238
455
|
const seenProperty = /* @__PURE__ */ new Set();
|
|
@@ -282,7 +499,7 @@ function parallel(children, timing, id) {
|
|
|
282
499
|
return new ParallelNode(children, timing, id);
|
|
283
500
|
}
|
|
284
501
|
|
|
285
|
-
// src/
|
|
502
|
+
// src/nodes/sequence.ts
|
|
286
503
|
var SequenceNode = class extends CompositionNode {
|
|
287
504
|
constructor(children, timing, id) {
|
|
288
505
|
super(children, timing, id);
|
|
@@ -301,12 +518,12 @@ function sequence(children, timing, id) {
|
|
|
301
518
|
return new SequenceNode(children, timing, id);
|
|
302
519
|
}
|
|
303
520
|
|
|
304
|
-
// src/
|
|
521
|
+
// src/nodes/stagger.ts
|
|
305
522
|
var StaggerNode = class extends CompositionNode {
|
|
306
|
-
constructor(children,
|
|
307
|
-
super(children,
|
|
523
|
+
constructor(children, offset, timing, id) {
|
|
524
|
+
super(children, timing, id);
|
|
308
525
|
this.type = "STAGGER";
|
|
309
|
-
this.offset =
|
|
526
|
+
this.offset = offset;
|
|
310
527
|
if (children.length === 0) {
|
|
311
528
|
this.duration = 0;
|
|
312
529
|
} else {
|
|
@@ -322,10 +539,81 @@ var StaggerNode = class extends CompositionNode {
|
|
|
322
539
|
}
|
|
323
540
|
}
|
|
324
541
|
};
|
|
325
|
-
function stagger(children,
|
|
326
|
-
return new StaggerNode(children,
|
|
542
|
+
function stagger(children, offset, timing, id) {
|
|
543
|
+
return new StaggerNode(children, offset, timing, id);
|
|
327
544
|
}
|
|
328
545
|
|
|
546
|
+
// src/ani/core/interface/timeline_interface.ts
|
|
547
|
+
var TimelineBase = class {
|
|
548
|
+
constructor(rootNode) {
|
|
549
|
+
this.rootNode = rootNode;
|
|
550
|
+
this._currentExecutionPlan = null;
|
|
551
|
+
this.duration = rootNode.duration;
|
|
552
|
+
this._baseExecutionPlan = this._constructExecutionPlan(rootNode);
|
|
553
|
+
this.play = this.play.bind(this);
|
|
554
|
+
this.pause = this.pause.bind(this);
|
|
555
|
+
this.seek = this.seek.bind(this);
|
|
556
|
+
this.reset = this.reset.bind(this);
|
|
557
|
+
this.resume = this.resume.bind(this);
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* flatten the AST into a linear execution plan.
|
|
561
|
+
*/
|
|
562
|
+
_constructExecutionPlan(rootNode) {
|
|
563
|
+
const plan = [];
|
|
564
|
+
rootNode.construct(plan, 0);
|
|
565
|
+
return plan;
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Merges the base plan with runtime dynamic overrides.
|
|
569
|
+
*/
|
|
570
|
+
_resolveExecutionPlan(keyframes, durations) {
|
|
571
|
+
if (!keyframes && !durations) {
|
|
572
|
+
return [...this._baseExecutionPlan];
|
|
573
|
+
}
|
|
574
|
+
const segmentNodes = this._baseExecutionPlan.filter(
|
|
575
|
+
(segment) => segment.node.type === "SEGMENT"
|
|
576
|
+
);
|
|
577
|
+
const segLength = segmentNodes.length;
|
|
578
|
+
if (keyframes && keyframes.length !== segLength) {
|
|
579
|
+
throw new Error(
|
|
580
|
+
`[Timeline] Keyframe mismatch: Expected ${segLength}, received ${keyframes.length}.`
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
if (durations && durations.length !== segLength) {
|
|
584
|
+
throw new Error(
|
|
585
|
+
`[Timeline] Duration mismatch: Expected ${segLength}, received ${durations.length}.`
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
const newPlan = [];
|
|
589
|
+
let keyframeIndex = 0;
|
|
590
|
+
for (const segment of this._baseExecutionPlan) {
|
|
591
|
+
if (segment.node.type === "SEGMENT") {
|
|
592
|
+
const dynamicTo = keyframes?.[keyframeIndex];
|
|
593
|
+
const dynamicDuration = durations?.[keyframeIndex];
|
|
594
|
+
const newSegmentProps = {
|
|
595
|
+
...segment.node.props,
|
|
596
|
+
...dynamicTo && dynamicTo !== "keep" && {
|
|
597
|
+
to: dynamicTo
|
|
598
|
+
},
|
|
599
|
+
...dynamicDuration && dynamicDuration !== "keep" && {
|
|
600
|
+
duration: dynamicDuration
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
const newSegment = new SegmentNode(
|
|
604
|
+
newSegmentProps,
|
|
605
|
+
segment.node.id
|
|
606
|
+
);
|
|
607
|
+
newPlan.push({ ...segment, node: newSegment });
|
|
608
|
+
keyframeIndex++;
|
|
609
|
+
} else {
|
|
610
|
+
newPlan.push({ ...segment });
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return newPlan;
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
|
|
329
617
|
// src/loop/clock.ts
|
|
330
618
|
var AnimationClock = class _AnimationClock {
|
|
331
619
|
constructor(maxDeltaTime) {
|
|
@@ -381,234 +669,174 @@ var AnimationClock = class _AnimationClock {
|
|
|
381
669
|
}
|
|
382
670
|
};
|
|
383
671
|
|
|
384
|
-
// src/
|
|
385
|
-
function
|
|
386
|
-
|
|
672
|
+
// src/ani/core/resolver.ts
|
|
673
|
+
function resolveGroup(group) {
|
|
674
|
+
if (Array.isArray(group)) {
|
|
675
|
+
return { keyMap: null, values: group };
|
|
676
|
+
}
|
|
677
|
+
const typedGroup = group;
|
|
678
|
+
const keys = Object.keys(typedGroup);
|
|
679
|
+
const keyMap = new Map(keys.map((key, i) => [key, i]));
|
|
680
|
+
const values = keys.map((key) => typedGroup[key]);
|
|
681
|
+
return { keyMap, values };
|
|
387
682
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
const t = Math.max(0, Math.min(localTime, segmentDef.duration));
|
|
392
|
-
const animeValues = [];
|
|
393
|
-
let allComplete = true;
|
|
394
|
-
const isMultipleTiming = Array.isArray(segmentDef.timing);
|
|
395
|
-
if (isMultipleTiming && segmentDef.timing.length !== segmentDef.from.length) {
|
|
396
|
-
throw new TypeError(
|
|
397
|
-
`[calculateSegmentState] timing does not correctly set. It requires multiple timing for ${segmentDef.from}, but received ${segmentDef.timing}`
|
|
398
|
-
);
|
|
683
|
+
function resolveStateToGroup(state, keyMap) {
|
|
684
|
+
if (!keyMap) {
|
|
685
|
+
return state;
|
|
399
686
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
687
|
+
const group = {};
|
|
688
|
+
for (const [key, index] of keyMap.entries()) {
|
|
689
|
+
group[key] = state[index];
|
|
690
|
+
}
|
|
691
|
+
return group;
|
|
692
|
+
}
|
|
693
|
+
function resolvePlanState(plan, initialValues, keyMap, targetTime, dt = 0) {
|
|
694
|
+
const nextState = [...initialValues];
|
|
695
|
+
let stateAtLastStartTime = [...initialValues];
|
|
696
|
+
for (const segment of plan) {
|
|
697
|
+
if (targetTime < segment.startTime) {
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
stateAtLastStartTime = [...nextState];
|
|
701
|
+
const { keyMap: segKeyMap, values: toValues } = resolveGroup(
|
|
702
|
+
segment.node.props.to
|
|
703
|
+
);
|
|
704
|
+
const isRecordAni = keyMap !== null;
|
|
705
|
+
let fromValues = [];
|
|
706
|
+
const timings = [];
|
|
707
|
+
const t = segment.node.props.timing;
|
|
708
|
+
const isRecordTiming = t && !(t instanceof TimingFunction);
|
|
709
|
+
if (isRecordAni) {
|
|
710
|
+
for (const key of segKeyMap.keys()) {
|
|
711
|
+
const index = keyMap.get(key);
|
|
712
|
+
fromValues.push(stateAtLastStartTime[index]);
|
|
713
|
+
if (isRecordTiming) {
|
|
714
|
+
timings.push(t[key]);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
} else {
|
|
718
|
+
fromValues = stateAtLastStartTime;
|
|
719
|
+
}
|
|
720
|
+
const localTime = targetTime - segment.startTime;
|
|
721
|
+
const segmentDef = {
|
|
722
|
+
from: fromValues,
|
|
723
|
+
to: toValues,
|
|
724
|
+
duration: segment.node.duration,
|
|
725
|
+
// default fallback = linear
|
|
726
|
+
timing: isRecordAni && isRecordTiming ? timings : t ?? T.linear()
|
|
727
|
+
};
|
|
728
|
+
const result = calculateSegmentState(localTime, segmentDef, dt);
|
|
729
|
+
const finalValues = result.isComplete ? toValues : result.values;
|
|
730
|
+
if (isRecordAni) {
|
|
731
|
+
let i = 0;
|
|
732
|
+
for (const key of segKeyMap.keys()) {
|
|
733
|
+
const stateIndex = keyMap.get(key);
|
|
734
|
+
if (stateIndex !== void 0 && stateIndex !== -1) {
|
|
735
|
+
nextState[stateIndex] = finalValues[i];
|
|
736
|
+
}
|
|
737
|
+
i++;
|
|
738
|
+
}
|
|
739
|
+
} else {
|
|
740
|
+
for (let i = 0; i < finalValues.length; i++) {
|
|
741
|
+
nextState[i] = finalValues[i];
|
|
742
|
+
}
|
|
411
743
|
}
|
|
412
744
|
}
|
|
413
|
-
return
|
|
414
|
-
values: animeValues,
|
|
415
|
-
isComplete: allComplete || isEndOfAnimation(t, segmentDef.duration)
|
|
416
|
-
};
|
|
745
|
+
return nextState;
|
|
417
746
|
}
|
|
418
747
|
|
|
419
|
-
// src/ani/timeline.ts
|
|
420
|
-
var
|
|
748
|
+
// src/ani/raf/timeline.ts
|
|
749
|
+
var RafAniTimeline = class extends TimelineBase {
|
|
421
750
|
constructor(rootNode, clock) {
|
|
422
|
-
|
|
423
|
-
this._currentExecutionPlan = null;
|
|
751
|
+
super(rootNode);
|
|
424
752
|
this._masterTime = 0;
|
|
753
|
+
this._delay = 0;
|
|
425
754
|
this._status = "IDLE";
|
|
426
755
|
this._currentConfig = null;
|
|
427
756
|
this._state = [];
|
|
428
757
|
this._initialState = [];
|
|
429
758
|
this._repeatCount = 0;
|
|
430
759
|
this._propertyKeyMap = null;
|
|
431
|
-
this._segmentStartStates = /* @__PURE__ */ new Map();
|
|
432
760
|
this._onUpdateCallbacks = /* @__PURE__ */ new Set();
|
|
433
|
-
this.duration = rootNode.duration;
|
|
434
|
-
this._baseExecutionPlan = this._constructExecutionPlan(rootNode);
|
|
435
761
|
this._clock = clock ?? AnimationClock.create();
|
|
436
|
-
this.play.bind(this);
|
|
437
|
-
this.pause.bind(this);
|
|
438
|
-
this.seek.bind(this);
|
|
439
|
-
this.resume.bind(this);
|
|
440
|
-
this.reset.bind(this);
|
|
762
|
+
this.play = this.play.bind(this);
|
|
763
|
+
this.pause = this.pause.bind(this);
|
|
764
|
+
this.seek = this.seek.bind(this);
|
|
765
|
+
this.resume = this.resume.bind(this);
|
|
766
|
+
this.reset = this.reset.bind(this);
|
|
441
767
|
}
|
|
442
|
-
/**
|
|
443
|
-
* Current animation running config.
|
|
444
|
-
*/
|
|
445
768
|
get currentConfig() {
|
|
446
769
|
return this._currentConfig;
|
|
447
770
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
}
|
|
455
|
-
const keyMap = new Map(Object.keys(group).map((key, i) => [key, i]));
|
|
456
|
-
const values = Object.values(group);
|
|
457
|
-
return { keyMap, values };
|
|
771
|
+
getCurrentValue() {
|
|
772
|
+
if (this._state.length === 0) return null;
|
|
773
|
+
return resolveStateToGroup(
|
|
774
|
+
this._state,
|
|
775
|
+
this._propertyKeyMap
|
|
776
|
+
);
|
|
458
777
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
_resolveStateToGroup(state) {
|
|
463
|
-
if (!this._propertyKeyMap) {
|
|
464
|
-
return state;
|
|
465
|
-
}
|
|
466
|
-
const group = {};
|
|
467
|
-
let i = 0;
|
|
468
|
-
for (const key of this._propertyKeyMap.keys()) {
|
|
469
|
-
group[key] = state[i];
|
|
470
|
-
i++;
|
|
778
|
+
_calculateStateAtTime(targetTime, dt = 0) {
|
|
779
|
+
if (this._initialState.length === 0 || !this._currentExecutionPlan) {
|
|
780
|
+
return [];
|
|
471
781
|
}
|
|
472
|
-
return
|
|
782
|
+
return resolvePlanState(
|
|
783
|
+
this._currentExecutionPlan,
|
|
784
|
+
this._initialState,
|
|
785
|
+
this._propertyKeyMap,
|
|
786
|
+
// Using the class property directly
|
|
787
|
+
targetTime,
|
|
788
|
+
dt
|
|
789
|
+
);
|
|
473
790
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
791
|
+
notify() {
|
|
792
|
+
for (const subscriber of this._onUpdateCallbacks) {
|
|
793
|
+
subscriber({
|
|
794
|
+
state: resolveStateToGroup(
|
|
795
|
+
this._state,
|
|
796
|
+
this._propertyKeyMap
|
|
797
|
+
),
|
|
798
|
+
status: this._status
|
|
799
|
+
});
|
|
800
|
+
}
|
|
481
801
|
}
|
|
482
802
|
/**
|
|
483
|
-
*
|
|
803
|
+
* @private Internal clock subscription callback.
|
|
484
804
|
*/
|
|
485
|
-
|
|
486
|
-
if (this.
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
if (targetTime < segment.startTime) {
|
|
493
|
-
continue;
|
|
494
|
-
}
|
|
495
|
-
if (!segment.node.props.timing) {
|
|
496
|
-
throw new Error(
|
|
497
|
-
`[Timeline] timing should be provided. Please specify timing using a.timing.(...). Check target segment: ${JSON.stringify(segment, null, 2)}.`,
|
|
498
|
-
{ cause: segment }
|
|
499
|
-
);
|
|
500
|
-
}
|
|
501
|
-
stateAtLastStartTime = [...nextState];
|
|
502
|
-
const { keyMap, values: toValues } = this._resolveGroup(
|
|
503
|
-
segment.node.props.to
|
|
504
|
-
);
|
|
505
|
-
const isRecordAni = this._propertyKeyMap !== null && keyMap !== null;
|
|
506
|
-
let fromValues = [];
|
|
507
|
-
if (isRecordAni) {
|
|
508
|
-
for (const key of keyMap.keys()) {
|
|
509
|
-
fromValues.push(
|
|
510
|
-
stateAtLastStartTime[this._propertyKeyMap.get(key)]
|
|
511
|
-
);
|
|
512
|
-
}
|
|
513
|
-
} else {
|
|
514
|
-
fromValues = stateAtLastStartTime;
|
|
515
|
-
}
|
|
516
|
-
let finalAnimeValues = [];
|
|
517
|
-
const localTime = targetTime - segment.startTime;
|
|
518
|
-
const segmentDef = {
|
|
519
|
-
from: fromValues,
|
|
520
|
-
to: toValues,
|
|
521
|
-
duration: segment.node.duration,
|
|
522
|
-
timing: segment.node.props.timing
|
|
523
|
-
};
|
|
524
|
-
const segmentState = calculateSegmentState(
|
|
525
|
-
localTime,
|
|
526
|
-
segmentDef,
|
|
527
|
-
dt
|
|
528
|
-
);
|
|
529
|
-
if (segmentState.isComplete) {
|
|
530
|
-
finalAnimeValues = toValues;
|
|
805
|
+
update(dt) {
|
|
806
|
+
if (this._status !== "PLAYING") return;
|
|
807
|
+
if (this._delay > 0) {
|
|
808
|
+
this._delay -= dt;
|
|
809
|
+
if (this._delay < 0) {
|
|
810
|
+
dt = -this._delay;
|
|
811
|
+
this._delay = 0;
|
|
531
812
|
} else {
|
|
532
|
-
|
|
813
|
+
return;
|
|
533
814
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
815
|
+
}
|
|
816
|
+
this._masterTime += dt;
|
|
817
|
+
if (this._masterTime >= this.duration) this._masterTime = this.duration;
|
|
818
|
+
this._state = this._calculateStateAtTime(this._masterTime, dt);
|
|
819
|
+
this.notify();
|
|
820
|
+
if (isEndOfAnimation(this._masterTime, this.duration)) {
|
|
821
|
+
this._repeatCount += 1;
|
|
822
|
+
const noRepeat = (this._currentConfig.repeat ?? 0) === 0;
|
|
823
|
+
if (noRepeat) {
|
|
824
|
+
this._status = "ENDED";
|
|
825
|
+
this._clock.unsubscribe(this);
|
|
826
|
+
this.notify();
|
|
544
827
|
} else {
|
|
545
|
-
|
|
546
|
-
nextState[i] = finalAnimeValues[i];
|
|
547
|
-
}
|
|
828
|
+
this.play(this._currentConfig);
|
|
548
829
|
}
|
|
549
830
|
}
|
|
550
|
-
return nextState;
|
|
551
|
-
}
|
|
552
|
-
_resolveExecutionPlan(keyframes, durations) {
|
|
553
|
-
if (!keyframes && !durations) {
|
|
554
|
-
return [...this._baseExecutionPlan];
|
|
555
|
-
}
|
|
556
|
-
const segmentNodes = this._baseExecutionPlan.filter(
|
|
557
|
-
(segment) => segment.node.type === "SEGMENT"
|
|
558
|
-
);
|
|
559
|
-
const segLength = segmentNodes.length;
|
|
560
|
-
if (keyframes && keyframes.length !== segLength) {
|
|
561
|
-
throw new Error(
|
|
562
|
-
`Timeline keyframe mismatch: Expected ${segLength} keyframes, but received ${keyframes.length}.`
|
|
563
|
-
);
|
|
564
|
-
}
|
|
565
|
-
if (durations && durations.length !== segLength) {
|
|
566
|
-
throw new Error(
|
|
567
|
-
`Timeline keyframe mismatch: Expected ${segLength} durations, but received ${durations.length}.`
|
|
568
|
-
);
|
|
569
|
-
}
|
|
570
|
-
const newPlan = [];
|
|
571
|
-
let keyframeIndex = 0;
|
|
572
|
-
for (const segment of this._baseExecutionPlan) {
|
|
573
|
-
if (segment.node.type === "SEGMENT") {
|
|
574
|
-
const dynamicTo = keyframes?.[keyframeIndex];
|
|
575
|
-
const dynamicDuration = durations?.[keyframeIndex];
|
|
576
|
-
const newSegmentProps = {
|
|
577
|
-
...segment.node.props,
|
|
578
|
-
// >> dynamic to
|
|
579
|
-
...dynamicTo && {
|
|
580
|
-
to: dynamicTo === "keep" ? segment.node.props.to : dynamicTo
|
|
581
|
-
},
|
|
582
|
-
// >> dynamic duration
|
|
583
|
-
...dynamicDuration && {
|
|
584
|
-
duration: dynamicDuration === "keep" ? segment.node.props.duration : dynamicDuration
|
|
585
|
-
}
|
|
586
|
-
};
|
|
587
|
-
const newSegment = new SegmentNode(
|
|
588
|
-
newSegmentProps,
|
|
589
|
-
segment.node.id
|
|
590
|
-
);
|
|
591
|
-
newPlan.push({ ...segment, node: newSegment });
|
|
592
|
-
keyframeIndex++;
|
|
593
|
-
} else {
|
|
594
|
-
newPlan.push({ ...segment });
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
return newPlan;
|
|
598
|
-
}
|
|
599
|
-
notify() {
|
|
600
|
-
for (const subscriber of this._onUpdateCallbacks) {
|
|
601
|
-
subscriber({
|
|
602
|
-
state: this._resolveStateToGroup(this._state),
|
|
603
|
-
status: this._status
|
|
604
|
-
});
|
|
605
|
-
}
|
|
606
831
|
}
|
|
832
|
+
/**
|
|
833
|
+
* Plays animation.
|
|
834
|
+
* @param config {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame RequestAnimationFrame API} based config.
|
|
835
|
+
* @param canBeIntercepted if `true` animation can be intercepted even if already animation started.
|
|
836
|
+
*/
|
|
607
837
|
play(config, canBeIntercepted = true) {
|
|
608
|
-
if (this._status === "PLAYING" && !canBeIntercepted)
|
|
609
|
-
|
|
610
|
-
}
|
|
611
|
-
const isRepeating = this._currentConfig?.repeat && this._currentConfig?.repeat >= 1;
|
|
838
|
+
if (this._status === "PLAYING" && !canBeIntercepted) return;
|
|
839
|
+
const isRepeating = (this._currentConfig?.repeat ?? 0) >= 1;
|
|
612
840
|
const savedRepeatCount = isRepeating ? this._repeatCount : 0;
|
|
613
841
|
this.reset(false);
|
|
614
842
|
this._repeatCount = savedRepeatCount;
|
|
@@ -617,17 +845,20 @@ var Timeline = class {
|
|
|
617
845
|
return;
|
|
618
846
|
}
|
|
619
847
|
this._currentConfig = config;
|
|
848
|
+
if (this._repeatCount === 0) {
|
|
849
|
+
this._delay = (this._currentConfig.delay ?? 0) * 1e-3;
|
|
850
|
+
}
|
|
620
851
|
this._currentExecutionPlan = this._resolveExecutionPlan(
|
|
621
852
|
config.keyframes,
|
|
622
853
|
config.durations
|
|
623
854
|
);
|
|
624
|
-
const { keyMap
|
|
625
|
-
this._propertyKeyMap =
|
|
855
|
+
const { keyMap, values } = resolveGroup(config.from);
|
|
856
|
+
this._propertyKeyMap = keyMap;
|
|
626
857
|
this._state = values;
|
|
627
858
|
this._initialState = values;
|
|
628
859
|
this._status = "PLAYING";
|
|
629
860
|
this._clock.subscribe(this);
|
|
630
|
-
this.
|
|
861
|
+
this.notify();
|
|
631
862
|
}
|
|
632
863
|
pause() {
|
|
633
864
|
this._status = "PAUSED";
|
|
@@ -642,16 +873,14 @@ var Timeline = class {
|
|
|
642
873
|
this._status = "IDLE";
|
|
643
874
|
this._currentConfig = null;
|
|
644
875
|
this._masterTime = 0;
|
|
876
|
+
this._delay = 0;
|
|
645
877
|
this._state = [];
|
|
646
878
|
this._initialState = [];
|
|
647
879
|
this._propertyKeyMap = null;
|
|
648
|
-
this._segmentStartStates.clear();
|
|
649
880
|
this._currentExecutionPlan = null;
|
|
650
881
|
this._clock.unsubscribe(this);
|
|
651
882
|
this._repeatCount = 0;
|
|
652
|
-
if (notify)
|
|
653
|
-
this.notify();
|
|
654
|
-
}
|
|
883
|
+
if (notify) this.notify();
|
|
655
884
|
}
|
|
656
885
|
seek(targetTime) {
|
|
657
886
|
if (this._status === "PLAYING" || this._status === "ENDED") {
|
|
@@ -662,78 +891,50 @@ var Timeline = class {
|
|
|
662
891
|
this._state = this._calculateStateAtTime(seekTime, 0);
|
|
663
892
|
this.notify();
|
|
664
893
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
894
|
+
/**
|
|
895
|
+
* When timeline updates, subscribes on update callback.
|
|
896
|
+
* @param callback Subscription callback.
|
|
897
|
+
* @returns Unsubscribe.
|
|
898
|
+
*/
|
|
669
899
|
onUpdate(callback) {
|
|
670
900
|
this._onUpdateCallbacks.add(callback);
|
|
671
901
|
return () => {
|
|
672
902
|
this._onUpdateCallbacks.delete(callback);
|
|
673
903
|
};
|
|
674
904
|
}
|
|
675
|
-
update(dt) {
|
|
676
|
-
if (this._status !== "PLAYING") {
|
|
677
|
-
return;
|
|
678
|
-
}
|
|
679
|
-
this._masterTime += dt;
|
|
680
|
-
if (this._masterTime >= this.duration) {
|
|
681
|
-
this._masterTime = this.duration;
|
|
682
|
-
}
|
|
683
|
-
this._state = this._calculateStateAtTime(this._masterTime, dt);
|
|
684
|
-
this.notify();
|
|
685
|
-
if (isEndOfAnimation(this._masterTime, this.duration)) {
|
|
686
|
-
this._repeatCount += 1;
|
|
687
|
-
if (!this._currentConfig) {
|
|
688
|
-
throw new Error(
|
|
689
|
-
`[Timeline] currentConfig can not be null when update(dt)`
|
|
690
|
-
);
|
|
691
|
-
}
|
|
692
|
-
const noRepeat = (this._currentConfig.repeat ?? 0) === 0;
|
|
693
|
-
if (noRepeat) {
|
|
694
|
-
this._status = "ENDED";
|
|
695
|
-
this._clock.unsubscribe(this);
|
|
696
|
-
this.notify();
|
|
697
|
-
} else {
|
|
698
|
-
this.play(this._currentConfig);
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
905
|
};
|
|
703
|
-
function
|
|
704
|
-
return new
|
|
906
|
+
function rafTimeline(rootNode, clock) {
|
|
907
|
+
return new RafAniTimeline(rootNode, clock);
|
|
705
908
|
}
|
|
706
909
|
|
|
707
|
-
// src/ani/states.ts
|
|
910
|
+
// src/ani/raf/states.ts
|
|
708
911
|
function createStates(config) {
|
|
709
912
|
let State = config.initial;
|
|
710
|
-
let
|
|
913
|
+
let Timeline = rafTimeline(
|
|
711
914
|
config.states[State],
|
|
712
915
|
config.clock
|
|
713
916
|
);
|
|
714
917
|
const subs = /* @__PURE__ */ new Set();
|
|
715
|
-
const notify = (
|
|
918
|
+
const notify = (timeline) => {
|
|
716
919
|
for (const Sub of subs) {
|
|
717
|
-
Sub(
|
|
920
|
+
Sub(timeline);
|
|
718
921
|
}
|
|
719
922
|
};
|
|
720
923
|
return {
|
|
721
|
-
timeline: () =>
|
|
924
|
+
timeline: () => Timeline,
|
|
925
|
+
state: () => State,
|
|
722
926
|
onTimelineChange(callback) {
|
|
723
927
|
subs.add(callback);
|
|
724
928
|
return () => subs.delete(callback);
|
|
725
929
|
},
|
|
726
930
|
transitionTo(newState, timelineConfig, canBeIntercepted) {
|
|
727
|
-
if (!config.states[newState] || State === newState) {
|
|
728
|
-
return;
|
|
729
|
-
}
|
|
730
931
|
const from = timelineConfig?.from ?? // 1. config
|
|
731
|
-
|
|
932
|
+
Timeline.getCurrentValue() ?? // 2. last value
|
|
732
933
|
config.initialFrom;
|
|
733
934
|
State = newState;
|
|
734
|
-
|
|
735
|
-
notify(
|
|
736
|
-
|
|
935
|
+
Timeline = rafTimeline(config.states[State], config.clock);
|
|
936
|
+
notify(Timeline);
|
|
937
|
+
Timeline.play(
|
|
737
938
|
{
|
|
738
939
|
...timelineConfig,
|
|
739
940
|
from
|
|
@@ -744,77 +945,6 @@ function createStates(config) {
|
|
|
744
945
|
};
|
|
745
946
|
}
|
|
746
947
|
|
|
747
|
-
// src/event/manager.ts
|
|
748
|
-
var EventManager = class _EventManager {
|
|
749
|
-
constructor(supportedEvents) {
|
|
750
|
-
this.supportedEvents = supportedEvents;
|
|
751
|
-
this._element = null;
|
|
752
|
-
this._animeGetter = null;
|
|
753
|
-
this.setAnimeGetter = (animeGetter) => {
|
|
754
|
-
this._animeGetter = animeGetter;
|
|
755
|
-
};
|
|
756
|
-
this.eventMap = /* @__PURE__ */ new Map();
|
|
757
|
-
this.withAnimeValue = (listener) => {
|
|
758
|
-
const withAnime = (e) => {
|
|
759
|
-
listener(this.animeGetter(), e);
|
|
760
|
-
};
|
|
761
|
-
return withAnime;
|
|
762
|
-
};
|
|
763
|
-
this.add = (eventName, listener, options) => {
|
|
764
|
-
const withAnime = this.withAnimeValue(listener);
|
|
765
|
-
this.eventMap.set(eventName, withAnime);
|
|
766
|
-
this.targetElement.addEventListener(
|
|
767
|
-
eventName,
|
|
768
|
-
this.eventMap.get(eventName),
|
|
769
|
-
options
|
|
770
|
-
);
|
|
771
|
-
};
|
|
772
|
-
this.cleanupOne = (eventName) => {
|
|
773
|
-
const removeListener = this.eventMap.get(eventName);
|
|
774
|
-
if (!removeListener) return false;
|
|
775
|
-
this.targetElement.removeEventListener(eventName, removeListener);
|
|
776
|
-
return true;
|
|
777
|
-
};
|
|
778
|
-
this.cleanupAll = () => {
|
|
779
|
-
const clearResponse = [];
|
|
780
|
-
for (const evtName of this.eventMap.keys()) {
|
|
781
|
-
const res = this.cleanupOne(evtName);
|
|
782
|
-
clearResponse.push(res);
|
|
783
|
-
}
|
|
784
|
-
return clearResponse.some((t) => t === false) === false;
|
|
785
|
-
};
|
|
786
|
-
this.attach = (handlers) => {
|
|
787
|
-
Object.entries(handlers).forEach(([eventKey, handler]) => {
|
|
788
|
-
this.add(
|
|
789
|
-
_EventManager.getEvtKey(eventKey),
|
|
790
|
-
handler
|
|
791
|
-
);
|
|
792
|
-
});
|
|
793
|
-
};
|
|
794
|
-
}
|
|
795
|
-
get targetElement() {
|
|
796
|
-
if (!this._element) throw new Error("EventManger, bind element first");
|
|
797
|
-
return this._element;
|
|
798
|
-
}
|
|
799
|
-
get animeGetter() {
|
|
800
|
-
if (!this._animeGetter)
|
|
801
|
-
throw new Error("EventManager, animeGetter should be provided");
|
|
802
|
-
return this._animeGetter;
|
|
803
|
-
}
|
|
804
|
-
/**
|
|
805
|
-
* get pure `{event_name}`
|
|
806
|
-
* @param key onX`{event_name}`
|
|
807
|
-
*/
|
|
808
|
-
static getEvtKey(key) {
|
|
809
|
-
const removed = key.substring(2, key.length);
|
|
810
|
-
const Capitalized = `${removed[0].toLowerCase()}${removed.substring(1, key.length)}`;
|
|
811
|
-
return Capitalized;
|
|
812
|
-
}
|
|
813
|
-
bind(element) {
|
|
814
|
-
this._element = element;
|
|
815
|
-
}
|
|
816
|
-
};
|
|
817
|
-
|
|
818
948
|
// src/style/create_sheet.ts
|
|
819
949
|
var TransformFunctionMap = {
|
|
820
950
|
// deg
|
|
@@ -902,39 +1032,285 @@ function createStyleSheet(animeStyleValue, resolver) {
|
|
|
902
1032
|
return styleAccumulator;
|
|
903
1033
|
}
|
|
904
1034
|
|
|
1035
|
+
// src/ani/waapi/compiler/resolver.ts
|
|
1036
|
+
function resolveStateAt(plan, initialFrom, targetTime, dt) {
|
|
1037
|
+
const { keyMap, values: initialValues } = resolveGroup(initialFrom);
|
|
1038
|
+
const rawResultState = resolvePlanState(
|
|
1039
|
+
plan,
|
|
1040
|
+
initialValues,
|
|
1041
|
+
keyMap,
|
|
1042
|
+
targetTime,
|
|
1043
|
+
dt
|
|
1044
|
+
);
|
|
1045
|
+
return resolveStateToGroup(rawResultState, keyMap);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// src/ani/waapi/compiler/timing_compiler.ts
|
|
1049
|
+
function compileTiming(timing) {
|
|
1050
|
+
if (!timing) {
|
|
1051
|
+
return "linear";
|
|
1052
|
+
}
|
|
1053
|
+
if (timing instanceof LinearTimingFunction) {
|
|
1054
|
+
return "linear";
|
|
1055
|
+
}
|
|
1056
|
+
if (timing instanceof BezierTimingFunction) {
|
|
1057
|
+
const { p2, p3 } = timing.opt;
|
|
1058
|
+
return `cubic-bezier(${p2.x}, ${p2.y}, ${p3.x}, ${p3.y})`;
|
|
1059
|
+
}
|
|
1060
|
+
return null;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// src/ani/waapi/compiler/keyframe_compiler.ts
|
|
1064
|
+
function compileToKeyframes(plan, initialFrom) {
|
|
1065
|
+
if (plan.length === 0) {
|
|
1066
|
+
return [];
|
|
1067
|
+
}
|
|
1068
|
+
const FPS = 60;
|
|
1069
|
+
const SAMPLE_RATE = 1 / FPS;
|
|
1070
|
+
const duration = Math.max(...plan.map((s) => s.endTime));
|
|
1071
|
+
if (duration === 0) {
|
|
1072
|
+
const state = resolveStateAt(plan, initialFrom, 0, SAMPLE_RATE);
|
|
1073
|
+
const style = createStyleSheet(state);
|
|
1074
|
+
return [
|
|
1075
|
+
{ offset: 0, ...style },
|
|
1076
|
+
{ offset: 1, ...style }
|
|
1077
|
+
];
|
|
1078
|
+
}
|
|
1079
|
+
const timePoints = /* @__PURE__ */ new Set([0, duration]);
|
|
1080
|
+
for (const seg of plan) {
|
|
1081
|
+
timePoints.add(seg.startTime);
|
|
1082
|
+
timePoints.add(seg.endTime);
|
|
1083
|
+
}
|
|
1084
|
+
const sortedTimes = Array.from(timePoints).sort((a2, b) => a2 - b);
|
|
1085
|
+
const keyframes = [];
|
|
1086
|
+
const getEasingForInterval = (t, nextT) => {
|
|
1087
|
+
const activeSegments = plan.filter(
|
|
1088
|
+
(s) => s.startTime <= t && s.endTime >= nextT
|
|
1089
|
+
);
|
|
1090
|
+
if (activeSegments.length === 0) return "linear";
|
|
1091
|
+
const timings = activeSegments.map((s) => s.node.props.timing).filter((t2) => t2 !== void 0);
|
|
1092
|
+
if (timings.length === 0) return "linear";
|
|
1093
|
+
const firstTiming = timings[0];
|
|
1094
|
+
const allSame = timings.every((t2) => t2 === firstTiming);
|
|
1095
|
+
if (allSame && firstTiming instanceof TimingFunction) {
|
|
1096
|
+
return compileTiming(firstTiming);
|
|
1097
|
+
}
|
|
1098
|
+
return null;
|
|
1099
|
+
};
|
|
1100
|
+
for (let i = 0; i < sortedTimes.length; i++) {
|
|
1101
|
+
const currT = sortedTimes[i];
|
|
1102
|
+
const state = resolveStateAt(plan, initialFrom, currT, SAMPLE_RATE);
|
|
1103
|
+
const style = createStyleSheet(state);
|
|
1104
|
+
const keyframe = {
|
|
1105
|
+
offset: currT / duration,
|
|
1106
|
+
...style
|
|
1107
|
+
};
|
|
1108
|
+
keyframes.push(keyframe);
|
|
1109
|
+
if (i < sortedTimes.length - 1) {
|
|
1110
|
+
const nextT = sortedTimes[i + 1];
|
|
1111
|
+
const easing = getEasingForInterval(currT, nextT);
|
|
1112
|
+
if (easing === null) {
|
|
1113
|
+
let sampleT = currT + SAMPLE_RATE;
|
|
1114
|
+
while (sampleT < nextT) {
|
|
1115
|
+
const sampleState = resolveStateAt(
|
|
1116
|
+
plan,
|
|
1117
|
+
initialFrom,
|
|
1118
|
+
sampleT,
|
|
1119
|
+
SAMPLE_RATE
|
|
1120
|
+
);
|
|
1121
|
+
const sampleStyle = createStyleSheet(
|
|
1122
|
+
sampleState
|
|
1123
|
+
);
|
|
1124
|
+
keyframes.push({
|
|
1125
|
+
offset: sampleT / duration,
|
|
1126
|
+
...sampleStyle,
|
|
1127
|
+
easing: "linear"
|
|
1128
|
+
});
|
|
1129
|
+
sampleT += SAMPLE_RATE;
|
|
1130
|
+
}
|
|
1131
|
+
keyframe.easing = "linear";
|
|
1132
|
+
} else {
|
|
1133
|
+
keyframe.easing = easing;
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
return keyframes;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
// src/ani/waapi/timeline.ts
|
|
1141
|
+
var WebAniTimeline = class extends TimelineBase {
|
|
1142
|
+
constructor(rootNode) {
|
|
1143
|
+
super(rootNode);
|
|
1144
|
+
this._animation = null;
|
|
1145
|
+
this._keyframes = [];
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* Plays animation.
|
|
1149
|
+
* @param target Target element.
|
|
1150
|
+
* @param config {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API Web Animations API} based config.
|
|
1151
|
+
*/
|
|
1152
|
+
play(target, config) {
|
|
1153
|
+
if (this._animation) {
|
|
1154
|
+
this._animation.cancel();
|
|
1155
|
+
}
|
|
1156
|
+
this._currentExecutionPlan = this._resolveExecutionPlan(
|
|
1157
|
+
config.keyframes,
|
|
1158
|
+
config.durations
|
|
1159
|
+
);
|
|
1160
|
+
this._keyframes = compileToKeyframes(
|
|
1161
|
+
this._currentExecutionPlan,
|
|
1162
|
+
config.from
|
|
1163
|
+
);
|
|
1164
|
+
if (this._keyframes.length === 0) {
|
|
1165
|
+
return null;
|
|
1166
|
+
}
|
|
1167
|
+
const totalDurationMs = this._currentExecutionPlan.reduce(
|
|
1168
|
+
(max, seg) => Math.max(max, seg.endTime),
|
|
1169
|
+
0
|
|
1170
|
+
) * 1e3;
|
|
1171
|
+
const effect = new KeyframeEffect(target, this._keyframes, {
|
|
1172
|
+
duration: totalDurationMs,
|
|
1173
|
+
iterations: config.repeat ?? 1,
|
|
1174
|
+
delay: config.delay ?? 0,
|
|
1175
|
+
fill: "forwards"
|
|
1176
|
+
});
|
|
1177
|
+
this._animation = new Animation(effect, document.timeline);
|
|
1178
|
+
this._animation.play();
|
|
1179
|
+
return this._animation;
|
|
1180
|
+
}
|
|
1181
|
+
pause() {
|
|
1182
|
+
this._animation?.pause();
|
|
1183
|
+
}
|
|
1184
|
+
resume() {
|
|
1185
|
+
this._animation?.play();
|
|
1186
|
+
}
|
|
1187
|
+
reset() {
|
|
1188
|
+
this._animation?.cancel();
|
|
1189
|
+
this._animation = null;
|
|
1190
|
+
}
|
|
1191
|
+
seek(targetTime) {
|
|
1192
|
+
if (this._animation) {
|
|
1193
|
+
this._animation.currentTime = targetTime * 1e3;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* Native animation object.
|
|
1198
|
+
*
|
|
1199
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Animation Animation}.
|
|
1200
|
+
*/
|
|
1201
|
+
get nativeAnimation() {
|
|
1202
|
+
return this._animation;
|
|
1203
|
+
}
|
|
1204
|
+
};
|
|
1205
|
+
function webTimeline(rootNode) {
|
|
1206
|
+
return new WebAniTimeline(rootNode);
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
// src/event/manager.ts
|
|
1210
|
+
var EventManager = class _EventManager {
|
|
1211
|
+
constructor(supportedEvents) {
|
|
1212
|
+
this.supportedEvents = supportedEvents;
|
|
1213
|
+
this._element = null;
|
|
1214
|
+
this._animeGetter = null;
|
|
1215
|
+
this.setAnimeGetter = (animeGetter) => {
|
|
1216
|
+
this._animeGetter = animeGetter;
|
|
1217
|
+
};
|
|
1218
|
+
this.eventMap = /* @__PURE__ */ new Map();
|
|
1219
|
+
this.withAnimeValue = (listener) => {
|
|
1220
|
+
const withAnime = (e) => {
|
|
1221
|
+
listener(this.animeGetter(), e);
|
|
1222
|
+
};
|
|
1223
|
+
return withAnime;
|
|
1224
|
+
};
|
|
1225
|
+
this.add = (eventName, listener, options) => {
|
|
1226
|
+
const withAnime = this.withAnimeValue(listener);
|
|
1227
|
+
this.eventMap.set(eventName, withAnime);
|
|
1228
|
+
this.targetElement.addEventListener(
|
|
1229
|
+
eventName,
|
|
1230
|
+
this.eventMap.get(eventName),
|
|
1231
|
+
options
|
|
1232
|
+
);
|
|
1233
|
+
};
|
|
1234
|
+
this.cleanupOne = (eventName) => {
|
|
1235
|
+
const removeListener = this.eventMap.get(eventName);
|
|
1236
|
+
if (!removeListener) return false;
|
|
1237
|
+
this.targetElement.removeEventListener(eventName, removeListener);
|
|
1238
|
+
return true;
|
|
1239
|
+
};
|
|
1240
|
+
this.cleanupAll = () => {
|
|
1241
|
+
const clearResponse = [];
|
|
1242
|
+
for (const evtName of this.eventMap.keys()) {
|
|
1243
|
+
const res = this.cleanupOne(evtName);
|
|
1244
|
+
clearResponse.push(res);
|
|
1245
|
+
}
|
|
1246
|
+
return clearResponse.some((t) => t === false) === false;
|
|
1247
|
+
};
|
|
1248
|
+
this.attach = (handlers) => {
|
|
1249
|
+
Object.entries(handlers).forEach(([eventKey, handler]) => {
|
|
1250
|
+
this.add(
|
|
1251
|
+
_EventManager.getEvtKey(eventKey),
|
|
1252
|
+
handler
|
|
1253
|
+
);
|
|
1254
|
+
});
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
get targetElement() {
|
|
1258
|
+
if (!this._element) throw new Error("EventManger, bind element first");
|
|
1259
|
+
return this._element;
|
|
1260
|
+
}
|
|
1261
|
+
get animeGetter() {
|
|
1262
|
+
if (!this._animeGetter)
|
|
1263
|
+
throw new Error("EventManager, animeGetter should be provided");
|
|
1264
|
+
return this._animeGetter;
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* get pure `{event_name}`
|
|
1268
|
+
* @param key onX`{event_name}`
|
|
1269
|
+
*/
|
|
1270
|
+
static getEvtKey(key) {
|
|
1271
|
+
const removed = key.substring(2, key.length);
|
|
1272
|
+
const Capitalized = `${removed[0].toLowerCase()}${removed.substring(1, key.length)}`;
|
|
1273
|
+
return Capitalized;
|
|
1274
|
+
}
|
|
1275
|
+
bind(element) {
|
|
1276
|
+
this._element = element;
|
|
1277
|
+
}
|
|
1278
|
+
};
|
|
1279
|
+
|
|
905
1280
|
// src/index.ts
|
|
906
1281
|
var a = {
|
|
907
1282
|
timing: T,
|
|
1283
|
+
dynamicTimeline: rafTimeline,
|
|
1284
|
+
timeline: webTimeline,
|
|
1285
|
+
/**
|
|
1286
|
+
* Create animation segment.
|
|
1287
|
+
*/
|
|
908
1288
|
ani,
|
|
909
|
-
|
|
1289
|
+
/**
|
|
1290
|
+
* Add delay
|
|
1291
|
+
*/
|
|
910
1292
|
delay,
|
|
911
1293
|
loop,
|
|
912
1294
|
parallel,
|
|
913
1295
|
sequence,
|
|
914
1296
|
stagger,
|
|
915
|
-
|
|
1297
|
+
createStates
|
|
916
1298
|
};
|
|
917
1299
|
export {
|
|
918
1300
|
AnimationClock,
|
|
919
|
-
|
|
920
|
-
CompositionNode,
|
|
1301
|
+
BezierTimingFunction,
|
|
921
1302
|
EventManager,
|
|
922
1303
|
LinearTimingFunction,
|
|
923
|
-
|
|
924
|
-
SegmentNode,
|
|
925
|
-
SequenceNode,
|
|
926
|
-
StaggerNode,
|
|
1304
|
+
RafAniTimeline,
|
|
927
1305
|
T,
|
|
928
|
-
|
|
1306
|
+
TimelineBase,
|
|
929
1307
|
TimingFunction,
|
|
1308
|
+
WebAniTimeline,
|
|
930
1309
|
a,
|
|
931
|
-
|
|
1310
|
+
calculateSegmentState,
|
|
1311
|
+
compileToKeyframes,
|
|
932
1312
|
createStates,
|
|
933
1313
|
createStyleSheet,
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
parallel,
|
|
937
|
-
sequence,
|
|
938
|
-
stagger,
|
|
939
|
-
timeline
|
|
1314
|
+
rafTimeline,
|
|
1315
|
+
webTimeline
|
|
940
1316
|
};
|