@xtia/timeline 1.0.7 → 1.0.9
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 +65 -17
- package/index.js +5 -15
- package/internal/easing.js +1 -4
- package/internal/emitters.d.ts +4 -2
- package/internal/emitters.js +11 -15
- package/internal/point.d.ts +1 -1
- package/internal/point.js +2 -6
- package/internal/range.d.ts +14 -4
- package/internal/range.js +16 -13
- package/internal/timeline.d.ts +21 -12
- package/internal/timeline.js +49 -30
- package/internal/tween.d.ts +3 -0
- package/internal/tween.js +3 -6
- package/internal/utils.js +1 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
**Timeline** is a general‑purpose, environment-agnostic choreography engine that lets you orchestrate any sequence of value changes; numbers, vectors, colour tokens, custom blendable objects, or arbitrary data structures.
|
|
6
6
|
|
|
7
7
|
* [API Reference](#reference)
|
|
8
|
+
* [Playground](https://stackblitz.com/edit/timeline-string-tween?file=src%2Fmain.ts)
|
|
8
9
|
|
|
9
10
|
## Basic Use:
|
|
10
11
|
|
|
@@ -21,7 +22,7 @@ timeline
|
|
|
21
22
|
.range(0, 1000)
|
|
22
23
|
.tween("#646", "#000")
|
|
23
24
|
.listen(
|
|
24
|
-
value =>
|
|
25
|
+
value => element.style.background = value
|
|
25
26
|
);
|
|
26
27
|
|
|
27
28
|
// add another tween to make a slow typing effect
|
|
@@ -29,15 +30,14 @@ const message = "Hi, planet!";
|
|
|
29
30
|
timeline
|
|
30
31
|
.range(500, 2000)
|
|
31
32
|
.tween(0, message.length)
|
|
33
|
+
.map(n => message.substring(0, n))
|
|
32
34
|
.listen(
|
|
33
|
-
|
|
35
|
+
s => element.textContent = s
|
|
34
36
|
);
|
|
35
37
|
|
|
36
38
|
// use an easing function
|
|
37
39
|
timeline
|
|
38
|
-
.
|
|
39
|
-
.delta(500)
|
|
40
|
-
.range(3000)
|
|
40
|
+
.range(0, 3000)
|
|
41
41
|
.ease("bounce")
|
|
42
42
|
.tween("50%", "0%")
|
|
43
43
|
.listen(
|
|
@@ -85,27 +85,41 @@ asPercent
|
|
|
85
85
|
n => progressBar.style.width = n
|
|
86
86
|
);
|
|
87
87
|
|
|
88
|
-
// apply easing
|
|
88
|
+
// apply easing
|
|
89
89
|
const eased = firstFiveSeconds.ease("easeInOut");
|
|
90
90
|
eased.listen(
|
|
91
91
|
v => console.log(`Eased value: ${v}`)
|
|
92
92
|
);
|
|
93
93
|
|
|
94
|
-
//
|
|
95
|
-
|
|
94
|
+
// chain them
|
|
95
|
+
range
|
|
96
96
|
.tween(0, 30)
|
|
97
97
|
.map(Math.floor)
|
|
98
98
|
.dedupe()
|
|
99
99
|
.tap(n => console.log("Showing frame #", n))
|
|
100
100
|
.map(n => `animation-frame-${n}.png`)
|
|
101
101
|
.listen(filename => img.src = filename);
|
|
102
|
+
|
|
103
|
+
// each step in a chain is a 'pure', independent emitter that emits a
|
|
104
|
+
// transformation of its parent's emissions
|
|
105
|
+
const filenameEmitter = range
|
|
106
|
+
.tween(0, 3)
|
|
107
|
+
.map(Math.floor)
|
|
108
|
+
.dedupe()
|
|
109
|
+
.map(n => `animation-frame-${n}.png`);
|
|
110
|
+
|
|
111
|
+
// filenameEmitter will emit filenames as the Timeline passes through 'range'.
|
|
112
|
+
// it can be listened directly or further transformed
|
|
113
|
+
const urlEmitter = filenameEmitter
|
|
114
|
+
.map(filename => `http://www.example.com/${filename}`);
|
|
115
|
+
|
|
102
116
|
```
|
|
103
117
|
|
|
104
118
|
Range objects also provide a `play()` method that instructs the Timeline to play through that particular range:
|
|
105
119
|
|
|
106
120
|
```ts
|
|
107
121
|
// play through the first two seconds of the Timeline
|
|
108
|
-
timeline
|
|
122
|
+
await timeline
|
|
109
123
|
.range(0, 2000)
|
|
110
124
|
.play();
|
|
111
125
|
```
|
|
@@ -158,7 +172,41 @@ timeline
|
|
|
158
172
|
|
|
159
173
|
## More on tweening
|
|
160
174
|
|
|
161
|
-
Tween emitters can interpolate numbers, arrays of numbers, strings, and objects with a method `blend(from: this, to: this): this
|
|
175
|
+
Tween emitters can interpolate numbers, arrays of numbers, strings, and objects with a method `blend(from: this, to: this): this`, by the progression value emitted by their parent.
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
const range = timeline.range(0, 2000);
|
|
179
|
+
|
|
180
|
+
// numbers
|
|
181
|
+
range
|
|
182
|
+
.ease("overshootIn")
|
|
183
|
+
.tween(300, 500)
|
|
184
|
+
.listen(v => element.scrollTop = v);
|
|
185
|
+
|
|
186
|
+
// number arrays
|
|
187
|
+
range
|
|
188
|
+
.tween([0, 180], [360, 180])
|
|
189
|
+
.listen((angles) => pieChart.setValues(angles));
|
|
190
|
+
|
|
191
|
+
// strings
|
|
192
|
+
range
|
|
193
|
+
.tween("#000000", "#ff00ff")
|
|
194
|
+
.listen(v => element.style.color = v);
|
|
195
|
+
|
|
196
|
+
// blendable objects
|
|
197
|
+
// (T extends { blend(from: this, to: this): this })
|
|
198
|
+
import { RGBA } from "@xtia/rgba";
|
|
199
|
+
range
|
|
200
|
+
.tween(RGBA.parse("#c971a7"), RGBA.parse("#fff"))
|
|
201
|
+
.listen(v => element.style.background = v);
|
|
202
|
+
|
|
203
|
+
import { Angle } from "@xtia/mezr";
|
|
204
|
+
range
|
|
205
|
+
.tween(Angle.degrees(45), Angle.turns(.5))
|
|
206
|
+
.map(a => `rotate(${a.asDegrees}deg)`)
|
|
207
|
+
.listen(v => element.style.transform = v);
|
|
208
|
+
|
|
209
|
+
```
|
|
162
210
|
|
|
163
211
|
#### String interpolation
|
|
164
212
|
* If the strings contain tweenable tokens (numbers, colour codes) and are otherwise identical, those tokens are interpolated
|
|
@@ -180,7 +228,7 @@ timeline
|
|
|
180
228
|
.listen(v => document.title = v);
|
|
181
229
|
```
|
|
182
230
|
|
|
183
|
-
|
|
231
|
+
Try out the [shadow tweening example at StackBlitz](https://stackblitz.com/edit/timeline-string-tween?file=src%2Fmain.ts)
|
|
184
232
|
|
|
185
233
|
## Autoplay and Looping Strategies
|
|
186
234
|
|
|
@@ -190,13 +238,13 @@ To create a Timeline that immediately starts playing, pass `true` to its constru
|
|
|
190
238
|
// immediately fade in an element
|
|
191
239
|
new Timeline(true)
|
|
192
240
|
.range(0, 1000)
|
|
193
|
-
.
|
|
241
|
+
.listen(v => element.style.opacity = v);
|
|
194
242
|
|
|
195
243
|
// note, an `animate(duration)` function is exported for
|
|
196
244
|
// disposable, single-use animations such as this:
|
|
197
245
|
import { animate } from "@xtia/timeline";
|
|
198
246
|
animate(1000)
|
|
199
|
-
.
|
|
247
|
+
.listen(v => element.style.opacity = v);
|
|
200
248
|
```
|
|
201
249
|
|
|
202
250
|
Normally a Timeline will simply stop playing when it reaches the end. This can be changed by passing a second argument (`endAction`) to the constructor.
|
|
@@ -279,7 +327,7 @@ resourceUrls.forEach(url => {
|
|
|
279
327
|
We can pass a second argument to `seek()` to perform a 'smooth seek' over the given duration. A third argument can provide an easing function for the smooth seek process:
|
|
280
328
|
|
|
281
329
|
```ts
|
|
282
|
-
timeline.seek(timeline.end, 400, "overshootIn");
|
|
330
|
+
await timeline.seek(timeline.end, 400, "overshootIn");
|
|
283
331
|
```
|
|
284
332
|
|
|
285
333
|
## Backward-compatibility
|
|
@@ -366,15 +414,15 @@ Performs an interruptable 'smooth seek' to a specified position, lasting `durati
|
|
|
366
414
|
|
|
367
415
|
Returns a Promise that will be resolved when the smooth seek is completed (or is interrupted by another seek\*).
|
|
368
416
|
|
|
369
|
-
\*
|
|
417
|
+
\* If a smooth seek is interrupted by another seek, the interrupted seek will immediately complete before the new seek is applied, to ensure any resulting state reflects expectations set by the first seek.
|
|
370
418
|
|
|
371
419
|
##### `play(): void`
|
|
372
420
|
|
|
373
|
-
Begins playing through the Timeline, from its current position, at (1000
|
|
421
|
+
Begins playing through the Timeline, from its current position, at (1000 × `timeScale`) units per second, updating 60 times per second.
|
|
374
422
|
|
|
375
423
|
##### `play(fps): void`
|
|
376
424
|
|
|
377
|
-
Begins playing through the Timeline, from its current position, at (1000
|
|
425
|
+
Begins playing through the Timeline, from its current position, at (1000 × `timeScale`) units per second, updating `fps` times per second.
|
|
378
426
|
|
|
379
427
|
##### `tween<T>(start, duration, apply, from, to, easer?): `[`ChainingInterface`](#chaininginterface-interface)
|
|
380
428
|
|
package/index.js
CHANGED
|
@@ -1,15 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
Object.defineProperty(exports, "animate", { enumerable: true, get: function () { return timeline_1.animate; } });
|
|
7
|
-
var point_1 = require("./internal/point");
|
|
8
|
-
Object.defineProperty(exports, "TimelinePoint", { enumerable: true, get: function () { return point_1.TimelinePoint; } });
|
|
9
|
-
var range_1 = require("./internal/range");
|
|
10
|
-
Object.defineProperty(exports, "TimelineRange", { enumerable: true, get: function () { return range_1.TimelineRange; } });
|
|
11
|
-
var emitters_1 = require("./internal/emitters");
|
|
12
|
-
Object.defineProperty(exports, "Emitter", { enumerable: true, get: function () { return emitters_1.Emitter; } });
|
|
13
|
-
Object.defineProperty(exports, "RangeProgression", { enumerable: true, get: function () { return emitters_1.RangeProgression; } });
|
|
14
|
-
var easing_1 = require("./internal/easing");
|
|
15
|
-
Object.defineProperty(exports, "easers", { enumerable: true, get: function () { return easing_1.easers; } });
|
|
1
|
+
export { Timeline, animate } from "./internal/timeline";
|
|
2
|
+
export { TimelinePoint } from "./internal/point";
|
|
3
|
+
export { TimelineRange } from "./internal/range";
|
|
4
|
+
export { Emitter, RangeProgression } from "./internal/emitters";
|
|
5
|
+
export { easers } from "./internal/easing";
|
package/internal/easing.js
CHANGED
package/internal/emitters.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Easer, easers } from "./easing";
|
|
2
|
-
import { Tweenable } from "./tween";
|
|
2
|
+
import { BlendableWith, Tweenable } from "./tween";
|
|
3
3
|
type Handler<T> = (value: T) => void;
|
|
4
4
|
export type ListenFunc<T> = (handler: Handler<T>) => UnsubscribeFunc;
|
|
5
5
|
export type UnsubscribeFunc = () => void;
|
|
@@ -8,6 +8,7 @@ export declare class Emitter<T> {
|
|
|
8
8
|
protected constructor(onListen: ListenFunc<T>);
|
|
9
9
|
/**
|
|
10
10
|
* Used by tap() to create a clone of an Emitter with a redirected onListen
|
|
11
|
+
*
|
|
11
12
|
* Should be overridden in all Emitter subclasses
|
|
12
13
|
* @see {@link TimelineRange.redirect}
|
|
13
14
|
* @param listen
|
|
@@ -123,6 +124,7 @@ export declare class RangeProgression extends Emitter<number> {
|
|
|
123
124
|
* @returns Listenable: emits interpolated values
|
|
124
125
|
*/
|
|
125
126
|
tween<T extends Tweenable>(from: T, to: T): Emitter<T>;
|
|
127
|
+
tween<T extends BlendableWith<T, R>, R>(from: T, to: R): Emitter<T>;
|
|
126
128
|
/**
|
|
127
129
|
* Creates a chainable progress emitter that quantises progress, as emitted by its parent, to the nearest of `steps` discrete values.
|
|
128
130
|
*
|
|
@@ -182,7 +184,7 @@ export declare class RangeProgression extends Emitter<number> {
|
|
|
182
184
|
* 1
|
|
183
185
|
* | /
|
|
184
186
|
* o| /
|
|
185
|
-
* u|/
|
|
187
|
+
* u|/ __ delta=.5
|
|
186
188
|
* t| /
|
|
187
189
|
* | /
|
|
188
190
|
* |___/__
|
package/internal/emitters.js
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const tween_1 = require("./tween");
|
|
6
|
-
const utils_1 = require("./utils");
|
|
7
|
-
class Emitter {
|
|
1
|
+
import { easers } from "./easing";
|
|
2
|
+
import { tweenValue } from "./tween";
|
|
3
|
+
import { clamp } from "./utils";
|
|
4
|
+
export class Emitter {
|
|
8
5
|
constructor(onListen) {
|
|
9
6
|
this.onListen = onListen;
|
|
10
7
|
/**
|
|
11
8
|
* Used by tap() to create a clone of an Emitter with a redirected onListen
|
|
9
|
+
*
|
|
12
10
|
* Should be overridden in all Emitter subclasses
|
|
13
11
|
* @see {@link TimelineRange.redirect}
|
|
14
12
|
* @param listen
|
|
@@ -124,8 +122,7 @@ class Emitter {
|
|
|
124
122
|
return this;
|
|
125
123
|
}
|
|
126
124
|
}
|
|
127
|
-
|
|
128
|
-
class RangeProgression extends Emitter {
|
|
125
|
+
export class RangeProgression extends Emitter {
|
|
129
126
|
constructor() {
|
|
130
127
|
super(...arguments);
|
|
131
128
|
this.redirect = (listen) => new RangeProgression(listen);
|
|
@@ -134,14 +131,14 @@ class RangeProgression extends Emitter {
|
|
|
134
131
|
if (!easer)
|
|
135
132
|
return this;
|
|
136
133
|
const easerFunc = typeof easer == "string"
|
|
137
|
-
?
|
|
134
|
+
? easers[easer]
|
|
138
135
|
: easer;
|
|
139
136
|
return new RangeProgression(easer ? (handler => this.onListen((progress) => {
|
|
140
137
|
handler(easerFunc(progress));
|
|
141
138
|
})) : h => this.onListen(h));
|
|
142
139
|
}
|
|
143
140
|
tween(from, to) {
|
|
144
|
-
return new Emitter(handler => this.onListen(progress => handler(
|
|
141
|
+
return new Emitter(handler => this.onListen(progress => handler(tweenValue(from, to, progress))));
|
|
145
142
|
}
|
|
146
143
|
/**
|
|
147
144
|
* Creates a chainable progress emitter that quantises progress, as emitted by its parent, to the nearest of `steps` discrete values.
|
|
@@ -156,7 +153,7 @@ class RangeProgression extends Emitter {
|
|
|
156
153
|
}
|
|
157
154
|
return new RangeProgression(handler => this.onListen(progress => {
|
|
158
155
|
const snapped = Math.round(progress * steps) / steps;
|
|
159
|
-
handler(
|
|
156
|
+
handler(clamp(snapped, 0, 1));
|
|
160
157
|
}));
|
|
161
158
|
}
|
|
162
159
|
/**
|
|
@@ -177,7 +174,7 @@ class RangeProgression extends Emitter {
|
|
|
177
174
|
* @returns Listenable: emits clamped progression values
|
|
178
175
|
*/
|
|
179
176
|
clamp(min = 0, max = 1) {
|
|
180
|
-
return new RangeProgression(handler => this.onListen(progress => handler(
|
|
177
|
+
return new RangeProgression(handler => this.onListen(progress => handler(clamp(progress, min, max))));
|
|
181
178
|
}
|
|
182
179
|
/**
|
|
183
180
|
* Creates a chainable progress emitter that maps incoming values to a repeating linear scale
|
|
@@ -237,7 +234,7 @@ class RangeProgression extends Emitter {
|
|
|
237
234
|
* 1
|
|
238
235
|
* | /
|
|
239
236
|
* o| /
|
|
240
|
-
* u|/
|
|
237
|
+
* u|/ __ delta=.5
|
|
241
238
|
* t| /
|
|
242
239
|
* | /
|
|
243
240
|
* |___/__
|
|
@@ -251,4 +248,3 @@ class RangeProgression extends Emitter {
|
|
|
251
248
|
return new RangeProgression(handler => this.onListen(value => handler((value + delta) % 1)));
|
|
252
249
|
}
|
|
253
250
|
}
|
|
254
|
-
exports.RangeProgression = RangeProgression;
|
package/internal/point.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Emitter, ListenFunc } from "./emitters";
|
|
|
2
2
|
import { TimelineRange } from "./range";
|
|
3
3
|
import { Timeline } from "./timeline";
|
|
4
4
|
export type PointEvent = {
|
|
5
|
-
direction: -1 | 1;
|
|
5
|
+
readonly direction: -1 | 1;
|
|
6
6
|
};
|
|
7
7
|
export declare class TimelinePoint extends Emitter<PointEvent> {
|
|
8
8
|
private timeline;
|
package/internal/point.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
exports.TimelinePoint = void 0;
|
|
4
|
-
const emitters_1 = require("./emitters");
|
|
5
|
-
class TimelinePoint extends emitters_1.Emitter {
|
|
1
|
+
import { Emitter } from "./emitters";
|
|
2
|
+
export class TimelinePoint extends Emitter {
|
|
6
3
|
/** @internal Manual construction of TimelinePoint is outside of the API contract and subject to undocumented change */
|
|
7
4
|
constructor(onListen, timeline,
|
|
8
5
|
/**
|
|
@@ -42,4 +39,3 @@ class TimelinePoint extends emitters_1.Emitter {
|
|
|
42
39
|
return this.timeline.point(this.position + timeOffset);
|
|
43
40
|
}
|
|
44
41
|
}
|
|
45
|
-
exports.TimelinePoint = TimelinePoint;
|
package/internal/range.d.ts
CHANGED
|
@@ -8,6 +8,10 @@ export declare class TimelineRange extends RangeProgression {
|
|
|
8
8
|
/** The duration of this range */
|
|
9
9
|
readonly duration: number;
|
|
10
10
|
private endPosition;
|
|
11
|
+
/** The point on the Timeline at which this range begins */
|
|
12
|
+
readonly start: TimelinePoint;
|
|
13
|
+
/** The point on the Timeline at which this range ends */
|
|
14
|
+
readonly end: TimelinePoint;
|
|
11
15
|
/** @internal Manual construction of RangeProgression is outside of the API contract and subject to undocumented change */
|
|
12
16
|
constructor(onListen: ListenFunc<number>, timeline: Timeline, startPosition: number,
|
|
13
17
|
/** The duration of this range */
|
|
@@ -52,9 +56,15 @@ export declare class TimelineRange extends RangeProgression {
|
|
|
52
56
|
* @returns true if the provided point is within the range
|
|
53
57
|
*/
|
|
54
58
|
contains(point: TimelinePoint): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Checks if a range is fully within this range
|
|
61
|
+
* @param range The range to check
|
|
62
|
+
* @returns true if the provided range is within the parent
|
|
63
|
+
*/
|
|
55
64
|
contains(range: TimelineRange): boolean;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
65
|
+
overlaps(range: TimelineRange): boolean;
|
|
66
|
+
overlaps(range: {
|
|
67
|
+
position: number;
|
|
68
|
+
duration: number;
|
|
69
|
+
}): boolean;
|
|
60
70
|
}
|
package/internal/range.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const point_1 = require("./point");
|
|
6
|
-
const utils_1 = require("./utils");
|
|
7
|
-
class TimelineRange extends emitters_1.RangeProgression {
|
|
1
|
+
import { RangeProgression } from "./emitters";
|
|
2
|
+
import { TimelinePoint } from "./point";
|
|
3
|
+
import { clamp } from "./utils";
|
|
4
|
+
export class TimelineRange extends RangeProgression {
|
|
8
5
|
/** @internal Manual construction of RangeProgression is outside of the API contract and subject to undocumented change */
|
|
9
6
|
constructor(onListen, timeline, startPosition,
|
|
10
7
|
/** The duration of this range */
|
|
@@ -14,9 +11,9 @@ class TimelineRange extends emitters_1.RangeProgression {
|
|
|
14
11
|
this.startPosition = startPosition;
|
|
15
12
|
this.duration = duration;
|
|
16
13
|
this.redirect = (listen) => new TimelineRange(listen, this.timeline, this.startPosition, this.duration);
|
|
17
|
-
this.start = timeline.point(startPosition);
|
|
18
|
-
this.end = timeline.point(startPosition + duration);
|
|
19
14
|
this.endPosition = startPosition + duration;
|
|
15
|
+
this.end = timeline.point(this.endPosition);
|
|
16
|
+
this.start = timeline.point(startPosition);
|
|
20
17
|
}
|
|
21
18
|
/**
|
|
22
19
|
* Creates two ranges by seperating one at a given point
|
|
@@ -58,7 +55,7 @@ class TimelineRange extends emitters_1.RangeProgression {
|
|
|
58
55
|
* @returns Listenable: this range will emit a progression value (0..1) when a `seek()` passes or intersects it
|
|
59
56
|
*/
|
|
60
57
|
grow(delta, anchor = 0) {
|
|
61
|
-
const clampedAnchor =
|
|
58
|
+
const clampedAnchor = clamp(anchor, 0, 1);
|
|
62
59
|
const leftDelta = -delta * (1 - clampedAnchor);
|
|
63
60
|
const rightDelta = delta * clampedAnchor;
|
|
64
61
|
const newStart = this.startPosition + leftDelta;
|
|
@@ -79,7 +76,7 @@ class TimelineRange extends emitters_1.RangeProgression {
|
|
|
79
76
|
if (factor <= 0) {
|
|
80
77
|
throw new RangeError('scale factor must be > 0');
|
|
81
78
|
}
|
|
82
|
-
const clampedAnchor =
|
|
79
|
+
const clampedAnchor = clamp(anchor, 0, 1);
|
|
83
80
|
const oldLen = this.endPosition - this.startPosition;
|
|
84
81
|
const pivot = this.startPosition + oldLen * clampedAnchor;
|
|
85
82
|
const newStart = pivot - (pivot - this.startPosition) * factor;
|
|
@@ -91,10 +88,16 @@ class TimelineRange extends emitters_1.RangeProgression {
|
|
|
91
88
|
return this.timeline.range(newStart, newEnd - newStart);
|
|
92
89
|
}
|
|
93
90
|
contains(target) {
|
|
94
|
-
const [targetStart, targetEnd] = target instanceof
|
|
91
|
+
const [targetStart, targetEnd] = target instanceof TimelinePoint
|
|
95
92
|
? [target.position, target.position]
|
|
96
93
|
: [target.startPosition, target.startPosition + target.duration];
|
|
97
94
|
return targetStart >= this.startPosition && targetEnd < this.endPosition;
|
|
98
95
|
}
|
|
96
|
+
overlaps(range) {
|
|
97
|
+
const [start, end] = range instanceof TimelineRange
|
|
98
|
+
? [range.startPosition, range.endPosition]
|
|
99
|
+
: [range.position, range.position + range.duration];
|
|
100
|
+
return Math.min(this.startPosition, this.endPosition) <= Math.max(start, end) &&
|
|
101
|
+
Math.max(this.startPosition, this.endPosition) >= Math.min(start, end);
|
|
102
|
+
}
|
|
99
103
|
}
|
|
100
|
-
exports.TimelineRange = TimelineRange;
|
package/internal/timeline.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Easer, easers } from "./easing";
|
|
2
|
+
import { RangeProgression } from "./emitters";
|
|
2
3
|
import { TimelinePoint } from "./point";
|
|
3
4
|
import { TimelineRange } from "./range";
|
|
4
5
|
import { Tweenable } from "./tween";
|
|
@@ -11,7 +12,7 @@ declare const EndAction: {
|
|
|
11
12
|
};
|
|
12
13
|
/**
|
|
13
14
|
* Creates an autoplaying Timeline and returns a range from it
|
|
14
|
-
* @param duration
|
|
15
|
+
* @param duration Animation duration, in milliseconds
|
|
15
16
|
* @returns Object representing a range on a single-use, autoplaying Timeline
|
|
16
17
|
*/
|
|
17
18
|
export declare function animate(duration: number): TimelineRange;
|
|
@@ -36,14 +37,22 @@ export declare class Timeline {
|
|
|
36
37
|
private smoothSeeker;
|
|
37
38
|
private seeking;
|
|
38
39
|
readonly start: TimelinePoint;
|
|
39
|
-
private
|
|
40
|
+
private progressionHandlers;
|
|
41
|
+
private _progression;
|
|
42
|
+
/**
|
|
43
|
+
* Listenable: emits a progression value (0..1) when the Timeline's internal
|
|
44
|
+
* position changes, and when the Timeline's total duration is extended
|
|
45
|
+
*
|
|
46
|
+
* **Experimental**
|
|
47
|
+
*/
|
|
48
|
+
get progression(): RangeProgression;
|
|
40
49
|
constructor();
|
|
41
50
|
/**
|
|
42
|
-
* @param autoplay Pass `true` to begin playing at (1000
|
|
51
|
+
* @param autoplay Pass `true` to begin playing at (1000 × this.timeScale) units per second immediately on creation
|
|
43
52
|
*/
|
|
44
53
|
constructor(autoplay: boolean);
|
|
45
54
|
/**
|
|
46
|
-
* Creates a Timeline that begins playing immediately at (1000
|
|
55
|
+
* Creates a Timeline that begins playing immediately at (1000 × this.timeScale) units per second
|
|
47
56
|
* @param autoplayFps Specifies frames per second
|
|
48
57
|
*/
|
|
49
58
|
constructor(autoplayFps: number);
|
|
@@ -51,12 +60,12 @@ export declare class Timeline {
|
|
|
51
60
|
* @param autoplay If this argument is `true`, the Timeline will begin playing immediately on creation. If the argument is a number, the Timeline will begin playing at the specified frames per second
|
|
52
61
|
* @param endAction Specifies what should happen when the final position is passed by `play()`/`autoplay`
|
|
53
62
|
*
|
|
54
|
-
* `"pause"`: **(default)** the Timeline will pause at its final position
|
|
55
|
-
* `"continue"`: The Timeline will continue progressing beyond its final position
|
|
56
|
-
* `"restart"`: The Timeline will seek back to 0 then forward to account for any overshoot and continue progressing
|
|
57
|
-
* `"wrap"`: The Timeline's position will continue to increase beyond the final position, but Points and Ranges will be activated as if looping
|
|
58
|
-
* `{restartAt: number}`: Like `"restart"` but seeking back to `restartAt` instead of 0
|
|
59
|
-
* `{wrapAt: number}`: Like `"wrap"` but as if restarting at `wrapAt` instead of 0
|
|
63
|
+
* * `"pause"`: **(default)** the Timeline will pause at its final position
|
|
64
|
+
* * `"continue"`: The Timeline will continue progressing beyond its final position
|
|
65
|
+
* * `"restart"`: The Timeline will seek back to 0 then forward to account for any overshoot and continue progressing
|
|
66
|
+
* * `"wrap"`: The Timeline's position will continue to increase beyond the final position, but Points and Ranges will be activated as if looping
|
|
67
|
+
* * `{restartAt: number}`: Like `"restart"` but seeking back to `restartAt` instead of 0
|
|
68
|
+
* * `{wrapAt: number}`: Like `"wrap"` but as if restarting at `wrapAt` instead of 0
|
|
60
69
|
*/
|
|
61
70
|
constructor(autoplay: boolean | number, endAction: {
|
|
62
71
|
wrapAt: number;
|
|
@@ -99,7 +108,7 @@ export declare class Timeline {
|
|
|
99
108
|
/**
|
|
100
109
|
* Smooth-seeks to a specified position
|
|
101
110
|
*
|
|
102
|
-
*
|
|
111
|
+
* Immediately completes and replaces any ongoing smooth-seek process on this Timeline
|
|
103
112
|
* @param toPosition
|
|
104
113
|
* @param duration Duration of the smooth-seek process in milliseconds
|
|
105
114
|
* @param easer Optional easing function for the smooth-seek process
|
|
@@ -111,7 +120,7 @@ export declare class Timeline {
|
|
|
111
120
|
private seekRanges;
|
|
112
121
|
private sortEntries;
|
|
113
122
|
/**
|
|
114
|
-
* Starts progression of the Timeline from its current position at (1000
|
|
123
|
+
* Starts progression of the Timeline from its current position at (1000 × this.timeScale) units per second
|
|
115
124
|
*/
|
|
116
125
|
play(): void;
|
|
117
126
|
play(fps: number): void;
|
package/internal/timeline.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const point_1 = require("./point");
|
|
6
|
-
const range_1 = require("./range");
|
|
7
|
-
const utils_1 = require("./utils");
|
|
1
|
+
import { RangeProgression } from "./emitters";
|
|
2
|
+
import { TimelinePoint } from "./point";
|
|
3
|
+
import { TimelineRange } from "./range";
|
|
4
|
+
import { clamp } from "./utils";
|
|
8
5
|
const default_fps = 60;
|
|
9
6
|
const EndAction = {
|
|
10
7
|
pause: 0,
|
|
@@ -14,13 +11,13 @@ const EndAction = {
|
|
|
14
11
|
};
|
|
15
12
|
/**
|
|
16
13
|
* Creates an autoplaying Timeline and returns a range from it
|
|
17
|
-
* @param duration
|
|
14
|
+
* @param duration Animation duration, in milliseconds
|
|
18
15
|
* @returns Object representing a range on a single-use, autoplaying Timeline
|
|
19
16
|
*/
|
|
20
|
-
function animate(duration) {
|
|
17
|
+
export function animate(duration) {
|
|
21
18
|
return new Timeline(true).range(0, duration);
|
|
22
19
|
}
|
|
23
|
-
class Timeline {
|
|
20
|
+
export class Timeline {
|
|
24
21
|
get currentTime() { return this._currentTime; }
|
|
25
22
|
set currentTime(v) {
|
|
26
23
|
this.seek(v);
|
|
@@ -31,6 +28,17 @@ class Timeline {
|
|
|
31
28
|
get end() {
|
|
32
29
|
return this.point(this._endPosition);
|
|
33
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Listenable: emits a progression value (0..1) when the Timeline's internal
|
|
33
|
+
* position changes, and when the Timeline's total duration is extended
|
|
34
|
+
*
|
|
35
|
+
* **Experimental**
|
|
36
|
+
*/
|
|
37
|
+
get progression() {
|
|
38
|
+
if (this._progression === null)
|
|
39
|
+
this._progression = new TimelineProgressionEmitter(this.progressionHandlers);
|
|
40
|
+
return this._progression;
|
|
41
|
+
}
|
|
34
42
|
constructor(autoplay = false, endAction = "pause") {
|
|
35
43
|
/**
|
|
36
44
|
* Multiplies the speed at which `play()` progresses through the Timeline
|
|
@@ -47,7 +55,8 @@ class Timeline {
|
|
|
47
55
|
this.smoothSeeker = null;
|
|
48
56
|
this.seeking = false;
|
|
49
57
|
this.start = this.point(0);
|
|
50
|
-
this.
|
|
58
|
+
this.progressionHandlers = [];
|
|
59
|
+
this._progression = null;
|
|
51
60
|
if (endAction == "loop")
|
|
52
61
|
endAction = "restart";
|
|
53
62
|
if (autoplay !== false) {
|
|
@@ -82,8 +91,10 @@ class Timeline {
|
|
|
82
91
|
* Listenable: this point will emit a PointEvent whenever a `seek()` reaches or passes it
|
|
83
92
|
*/
|
|
84
93
|
point(position) {
|
|
85
|
-
if (position > this._endPosition)
|
|
94
|
+
if (position > this._endPosition) {
|
|
86
95
|
this._endPosition = position;
|
|
96
|
+
this.progressionHandlers.slice().forEach(h => h(this._currentTime / position));
|
|
97
|
+
}
|
|
87
98
|
const handlers = [];
|
|
88
99
|
const data = {
|
|
89
100
|
handlers,
|
|
@@ -109,7 +120,7 @@ class Timeline {
|
|
|
109
120
|
}
|
|
110
121
|
};
|
|
111
122
|
};
|
|
112
|
-
return new
|
|
123
|
+
return new TimelinePoint(addHandler, this, position);
|
|
113
124
|
}
|
|
114
125
|
range(start = 0, optionalDuration) {
|
|
115
126
|
const startPoint = typeof start == "number"
|
|
@@ -117,9 +128,9 @@ class Timeline {
|
|
|
117
128
|
: start;
|
|
118
129
|
const startPosition = startPoint.position;
|
|
119
130
|
const duration = optionalDuration ?? this._endPosition - startPosition;
|
|
120
|
-
const endPosition = startPosition + duration;
|
|
121
|
-
if (endPosition > this._endPosition)
|
|
122
|
-
|
|
131
|
+
// const endPosition = startPosition + duration;
|
|
132
|
+
//if (endPosition > this._endPosition) this._endPosition = endPosition;
|
|
133
|
+
// ^ leave this to range's point() calls
|
|
123
134
|
const handlers = [];
|
|
124
135
|
const range = {
|
|
125
136
|
position: startPosition,
|
|
@@ -145,7 +156,7 @@ class Timeline {
|
|
|
145
156
|
}
|
|
146
157
|
};
|
|
147
158
|
};
|
|
148
|
-
return new
|
|
159
|
+
return new TimelineRange(addHandler, this, startPosition, duration);
|
|
149
160
|
}
|
|
150
161
|
getWrappedPosition(n) {
|
|
151
162
|
if (this.endAction.type !== EndAction.wrap)
|
|
@@ -172,7 +183,7 @@ class Timeline {
|
|
|
172
183
|
}
|
|
173
184
|
if (this.smoothSeeker !== null) {
|
|
174
185
|
this.smoothSeeker.pause();
|
|
175
|
-
// ensure any awaits are resolved for the previous seek
|
|
186
|
+
// ensure any awaits are resolved for the previous seek
|
|
176
187
|
this.smoothSeeker.seek(this.smoothSeeker.end);
|
|
177
188
|
this.smoothSeeker = null;
|
|
178
189
|
}
|
|
@@ -213,7 +224,6 @@ class Timeline {
|
|
|
213
224
|
throw e;
|
|
214
225
|
}
|
|
215
226
|
this._currentTime = toPosition;
|
|
216
|
-
this.positionHandlers.slice().forEach(h => h(toPosition));
|
|
217
227
|
this.seeking = false;
|
|
218
228
|
}
|
|
219
229
|
seekPoints(to) {
|
|
@@ -222,27 +232,25 @@ class Timeline {
|
|
|
222
232
|
const pointsBetween = this.points.filter(direction > 0
|
|
223
233
|
? p => p.position > from && p.position <= to
|
|
224
234
|
: p => p.position <= from && p.position > to);
|
|
235
|
+
const eventData = {
|
|
236
|
+
direction
|
|
237
|
+
};
|
|
225
238
|
pointsBetween.slice().forEach(p => {
|
|
226
239
|
this.seekRanges(p.position);
|
|
227
240
|
this._currentTime = p.position;
|
|
228
|
-
const eventData = {
|
|
229
|
-
direction
|
|
230
|
-
};
|
|
231
241
|
p.handlers.slice().forEach(h => h(eventData));
|
|
232
242
|
});
|
|
233
243
|
}
|
|
234
244
|
seekRanges(to) {
|
|
235
|
-
const
|
|
245
|
+
const seekRange = this.point(Math.min(this._currentTime, to))
|
|
246
|
+
.to(Math.max(this._currentTime, to));
|
|
236
247
|
this.ranges.slice().forEach((range) => {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
// filter ranges that overlap seeked range
|
|
240
|
-
if (Math.min(position, end) <= Math.max(to, fromTime)
|
|
241
|
-
&& Math.min(to, fromTime) <= Math.max(position, end)) {
|
|
242
|
-
let progress = (0, utils_1.clamp)((to - range.position) / range.duration, 0, 1);
|
|
248
|
+
if (seekRange.overlaps(range)) {
|
|
249
|
+
let progress = clamp((to - range.position) / range.duration, 0, 1);
|
|
243
250
|
range.handlers.slice().forEach(h => h(progress));
|
|
244
251
|
}
|
|
245
252
|
});
|
|
253
|
+
this.progressionHandlers.slice().forEach(h => h(this._currentTime / this._endPosition));
|
|
246
254
|
}
|
|
247
255
|
sortEntries(direction) {
|
|
248
256
|
this.currentSortDirection = direction;
|
|
@@ -344,7 +352,18 @@ class Timeline {
|
|
|
344
352
|
return this._currentTime;
|
|
345
353
|
}
|
|
346
354
|
}
|
|
347
|
-
|
|
355
|
+
class TimelineProgressionEmitter extends RangeProgression {
|
|
356
|
+
constructor(handlers) {
|
|
357
|
+
super((handler) => {
|
|
358
|
+
const unique = (n) => handler(n);
|
|
359
|
+
handlers.push(unique);
|
|
360
|
+
return () => {
|
|
361
|
+
const idx = handlers.indexOf(unique);
|
|
362
|
+
handlers.splice(idx, 1);
|
|
363
|
+
};
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
348
367
|
const sortEvents = (a, b) => {
|
|
349
368
|
return a.position - b.position;
|
|
350
369
|
};
|
package/internal/tween.d.ts
CHANGED
|
@@ -4,5 +4,8 @@ export type Tweenable = number | number[] | string | Blendable;
|
|
|
4
4
|
export interface Blendable {
|
|
5
5
|
blend(target: this, progress: number): this;
|
|
6
6
|
}
|
|
7
|
+
export interface BlendableWith<T, R> {
|
|
8
|
+
blend(target: R, progress: number): T;
|
|
9
|
+
}
|
|
7
10
|
/** @internal */
|
|
8
11
|
export declare function tweenValue<T extends Tweenable>(from: T, to: T, progress: number): T;
|
package/internal/tween.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.tweenValue = tweenValue;
|
|
4
|
-
const utils_1 = require("./utils");
|
|
1
|
+
import { clamp } from "./utils";
|
|
5
2
|
/** @internal */
|
|
6
|
-
function tweenValue(from, to, progress) {
|
|
3
|
+
export function tweenValue(from, to, progress) {
|
|
7
4
|
if (Array.isArray(from)) {
|
|
8
5
|
const toArr = to;
|
|
9
6
|
if (from.length != toArr.length)
|
|
@@ -89,7 +86,7 @@ function parseColour(code) {
|
|
|
89
86
|
function blendColours(from, to, bias) {
|
|
90
87
|
const fromColour = parseColour(from);
|
|
91
88
|
const toColour = parseColour(to);
|
|
92
|
-
const blended = fromColour.map((val, i) =>
|
|
89
|
+
const blended = fromColour.map((val, i) => clamp(blendNumbers(val, toColour[i], bias), 0, 255));
|
|
93
90
|
return ("#" + blended.map(n => Math.round(n).toString(16).padStart(2, "0")).join("")).replace(/ff$/, "");
|
|
94
91
|
}
|
|
95
92
|
const tweenableTokenRegex = /(#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\b|[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)/g;
|
package/internal/utils.js
CHANGED
|
@@ -1,6 +1,2 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.clamp = void 0;
|
|
4
1
|
/** @internal */
|
|
5
|
-
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
6
|
-
exports.clamp = clamp;
|
|
2
|
+
export const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|