@xtia/timeline 1.0.11 → 1.1.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 +40 -29
- package/internal/emitters.d.ts +24 -4
- package/internal/emitters.js +34 -4
- package/internal/point.d.ts +4 -1
- package/internal/point.js +1 -1
- package/internal/range.d.ts +16 -2
- package/internal/range.js +22 -3
- package/internal/timeline.js +5 -5
- package/internal/tween.d.ts +4 -2
- package/internal/tween.js +23 -10
- package/internal/utils.d.ts +1 -1
- package/internal/utils.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ const timeline = new Timeline();
|
|
|
21
21
|
timeline
|
|
22
22
|
.range(0, 1000)
|
|
23
23
|
.tween("#646", "#000")
|
|
24
|
-
.
|
|
24
|
+
.apply(
|
|
25
25
|
value => element.style.background = value
|
|
26
26
|
);
|
|
27
27
|
|
|
@@ -31,7 +31,7 @@ timeline
|
|
|
31
31
|
.range(500, 2000)
|
|
32
32
|
.tween(0, message.length)
|
|
33
33
|
.map(n => message.substring(0, n))
|
|
34
|
-
.
|
|
34
|
+
.apply(
|
|
35
35
|
s => element.textContent = s
|
|
36
36
|
);
|
|
37
37
|
|
|
@@ -40,7 +40,7 @@ timeline
|
|
|
40
40
|
.range(0, 3000)
|
|
41
41
|
.ease("bounce")
|
|
42
42
|
.tween("50%", "0%")
|
|
43
|
-
.
|
|
43
|
+
.apply(
|
|
44
44
|
value => element.style.marginLeft = value
|
|
45
45
|
);
|
|
46
46
|
|
|
@@ -56,11 +56,11 @@ timeline.play();
|
|
|
56
56
|
const firstFiveSeconds = timeline.range(0, 5000);
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
The range object is *
|
|
59
|
+
The range object is *applyable* and emits a progression value (between 0 and 1) when the Timeline's internal position passes through or over that period.
|
|
60
60
|
|
|
61
61
|
```ts
|
|
62
62
|
firstFiveSeconds
|
|
63
|
-
.
|
|
63
|
+
.apply(
|
|
64
64
|
value => console.log(`${value} is between 0 and 1`)
|
|
65
65
|
);
|
|
66
66
|
```
|
|
@@ -74,20 +74,20 @@ const asPercent = firstFiveSeconds.map(n => n * 100);
|
|
|
74
74
|
// use the result in a log message
|
|
75
75
|
asPercent
|
|
76
76
|
.map(n => n.toFixed(2))
|
|
77
|
-
.
|
|
77
|
+
.apply(
|
|
78
78
|
n => console.log(`We are ${n}% through the first five seconds`)
|
|
79
79
|
);
|
|
80
80
|
|
|
81
81
|
// and in a css property
|
|
82
82
|
asPercent
|
|
83
83
|
.map(n => `${n}%`)
|
|
84
|
-
.
|
|
84
|
+
.apply(
|
|
85
85
|
n => progressBar.style.width = n
|
|
86
86
|
);
|
|
87
87
|
|
|
88
88
|
// apply easing
|
|
89
89
|
const eased = firstFiveSeconds.ease("easeInOut");
|
|
90
|
-
eased.
|
|
90
|
+
eased.apply(
|
|
91
91
|
v => console.log(`Eased value: ${v}`)
|
|
92
92
|
);
|
|
93
93
|
|
|
@@ -98,7 +98,7 @@ range
|
|
|
98
98
|
.dedupe()
|
|
99
99
|
.tap(n => console.log("Showing frame #", n))
|
|
100
100
|
.map(n => `animation-frame-${n}.png`)
|
|
101
|
-
.
|
|
101
|
+
.apply(filename => img.src = filename);
|
|
102
102
|
|
|
103
103
|
// each step in a chain is a 'pure', independent emitter that emits a
|
|
104
104
|
// transformation of its parent's emissions
|
|
@@ -146,7 +146,7 @@ const sixSecondsIn = fiveSecondsdIn.delta(1000);
|
|
|
146
146
|
Points emit `PointEvent` objects when their position is reached or passed.
|
|
147
147
|
|
|
148
148
|
```ts
|
|
149
|
-
twoSecondsIn.
|
|
149
|
+
twoSecondsIn.apply(event => {
|
|
150
150
|
// event.direction (-1 | 1) tells us the direction of the seek that
|
|
151
151
|
// triggered the point. This allows for reversible point events
|
|
152
152
|
document.body.classList.toggle("someClass", event.direction > 0);
|
|
@@ -181,30 +181,30 @@ const range = timeline.range(0, 2000);
|
|
|
181
181
|
range
|
|
182
182
|
.ease("overshootIn")
|
|
183
183
|
.tween(300, 500)
|
|
184
|
-
.
|
|
184
|
+
.apply(v => element.scrollTop = v);
|
|
185
185
|
|
|
186
186
|
// number arrays
|
|
187
187
|
range
|
|
188
188
|
.tween([0, 180], [360, 180])
|
|
189
|
-
.
|
|
189
|
+
.apply((angles) => pieChart.setValues(angles));
|
|
190
190
|
|
|
191
191
|
// strings
|
|
192
192
|
range
|
|
193
193
|
.tween("#000000", "#ff00ff")
|
|
194
|
-
.
|
|
194
|
+
.apply(v => element.style.color = v);
|
|
195
195
|
|
|
196
196
|
// blendable objects
|
|
197
197
|
// (T extends { blend(from: this, to: this): this })
|
|
198
198
|
import { RGBA } from "@xtia/rgba";
|
|
199
199
|
range
|
|
200
200
|
.tween(RGBA.parse("#c971a7"), RGBA.parse("#fff"))
|
|
201
|
-
.
|
|
201
|
+
.apply(v => element.style.background = v);
|
|
202
202
|
|
|
203
203
|
import { Angle } from "@xtia/mezr";
|
|
204
204
|
range
|
|
205
205
|
.tween(Angle.degrees(45), Angle.turns(.5))
|
|
206
206
|
.map(a => `rotate(${a.asDegrees}deg)`)
|
|
207
|
-
.
|
|
207
|
+
.apply(v => element.style.transform = v);
|
|
208
208
|
|
|
209
209
|
```
|
|
210
210
|
|
|
@@ -218,14 +218,14 @@ timeline
|
|
|
218
218
|
.range(0, 2000)
|
|
219
219
|
.ease("elastic")
|
|
220
220
|
.tween("0px 0px 0px #0000", "15px 15px 20px #0005")
|
|
221
|
-
.
|
|
221
|
+
.apply(s => element.style.textShadow = s);
|
|
222
222
|
|
|
223
223
|
// text progress bar
|
|
224
224
|
timeline
|
|
225
225
|
.range(0, 2000)
|
|
226
226
|
.tween("--------", "########")
|
|
227
227
|
.dedupe()
|
|
228
|
-
.
|
|
228
|
+
.apply(v => document.title = v);
|
|
229
229
|
```
|
|
230
230
|
|
|
231
231
|
Try out the [shadow tweening example at StackBlitz](https://stackblitz.com/edit/timeline-string-tween?file=src%2Fmain.ts)
|
|
@@ -238,13 +238,13 @@ To create a Timeline that immediately starts playing, pass `true` to its constru
|
|
|
238
238
|
// immediately fade in an element
|
|
239
239
|
new Timeline(true)
|
|
240
240
|
.range(0, 1000)
|
|
241
|
-
.
|
|
241
|
+
.apply(v => element.style.opacity = v);
|
|
242
242
|
|
|
243
243
|
// note, an `animate(duration)` function is exported for
|
|
244
244
|
// disposable, single-use animations such as this:
|
|
245
245
|
import { animate } from "@xtia/timeline";
|
|
246
246
|
animate(1000)
|
|
247
|
-
.
|
|
247
|
+
.apply(v => element.style.opacity = v);
|
|
248
248
|
```
|
|
249
249
|
|
|
250
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.
|
|
@@ -302,19 +302,19 @@ window.addEventListener(
|
|
|
302
302
|
setInterval(() => timeline.seek(Date.now()), 1000);
|
|
303
303
|
timeline
|
|
304
304
|
.point(new Date("2026-10-31").getTime())
|
|
305
|
-
.
|
|
305
|
+
.apply(() => console.log("Happy anniversary 🏳️⚧️💗"));
|
|
306
306
|
|
|
307
307
|
// show a progress bar for loaded resources
|
|
308
308
|
const loadingTimeline = new Timeline();
|
|
309
309
|
loadingTimeline
|
|
310
310
|
.range(0, resourceUrls.length)
|
|
311
311
|
.tween("0%", "100%");
|
|
312
|
-
.
|
|
312
|
+
.apply(v => progressBar.style.width = v);
|
|
313
313
|
|
|
314
314
|
// and do something when they're loaded
|
|
315
315
|
loadingTimeline
|
|
316
316
|
.end
|
|
317
|
-
.
|
|
317
|
+
.apply(startGame);
|
|
318
318
|
|
|
319
319
|
// to drive it, just seek forward by 1 for each loaded resource
|
|
320
320
|
resourceUrls.forEach(url => {
|
|
@@ -435,7 +435,7 @@ timeline
|
|
|
435
435
|
.range(start, duration)
|
|
436
436
|
.ease(easer)
|
|
437
437
|
.tween(from, to)
|
|
438
|
-
.
|
|
438
|
+
.apply(apply);
|
|
439
439
|
```
|
|
440
440
|
|
|
441
441
|
Returns a [`ChainingInterface`](#chaininginterface-interface) representing the point at which the tween ends.
|
|
@@ -483,6 +483,13 @@ Creates a [`TimelineRange`](#timelinerange-interface) on the Timeline to which t
|
|
|
483
483
|
|
|
484
484
|
Creates a `TimelinePoint` at an offset from the this point.
|
|
485
485
|
|
|
486
|
+
##### `seek(): void`
|
|
487
|
+
|
|
488
|
+
Seeks the parent Timeline to this point.
|
|
489
|
+
|
|
490
|
+
##### `seek(duration: number, easer?: Easer): Promise<void>`
|
|
491
|
+
|
|
492
|
+
Smooth-seeks the parent Timeline to this point over a specified duration and resolves the returned Promise on completion.
|
|
486
493
|
|
|
487
494
|
|
|
488
495
|
|
|
@@ -501,7 +508,7 @@ Allows point listeners to undo effects when the Timeline is reversed.
|
|
|
501
508
|
```ts
|
|
502
509
|
timeline
|
|
503
510
|
.point(4000)
|
|
504
|
-
.
|
|
511
|
+
.apply(
|
|
505
512
|
event => element.classList.toggle(
|
|
506
513
|
"visible",
|
|
507
514
|
event.direction > 0
|
|
@@ -593,6 +600,10 @@ blend(from: this, to: this, progress: number): this
|
|
|
593
600
|
|
|
594
601
|
Creates an emitter that quantises progression emitted by the parent to the nearest of `steps` discrete values.
|
|
595
602
|
|
|
603
|
+
##### `sample<T>(values: ArrayLike<T>): `[`Emitter<T>`](#emittert-interface)
|
|
604
|
+
|
|
605
|
+
Creates an emitter that emits values from an array according to progression.
|
|
606
|
+
|
|
596
607
|
##### `threshold(threshold): RangeProgression`
|
|
597
608
|
|
|
598
609
|
Creates an emitter that emits 0 when the parent emits a value below `threshold` and 1 when a parent emission is equal to or greater than `threshold`.
|
|
@@ -639,9 +650,9 @@ range
|
|
|
639
650
|
.fork(branch => {
|
|
640
651
|
branch
|
|
641
652
|
.map(s => `Loading: ${s}`)
|
|
642
|
-
.
|
|
653
|
+
.apply(s => document.title = s)
|
|
643
654
|
})
|
|
644
|
-
.
|
|
655
|
+
.apply(v => progressBar.style.width = v);
|
|
645
656
|
```
|
|
646
657
|
|
|
647
658
|
|
|
@@ -651,7 +662,7 @@ range
|
|
|
651
662
|
|
|
652
663
|
#### Methods
|
|
653
664
|
|
|
654
|
-
##### `
|
|
665
|
+
##### `apply(handler: Handler<T>): UnsubscribeFunc`
|
|
655
666
|
|
|
656
667
|
Attaches a handler to the emitter and returns a function that will unsubscribe the handler.
|
|
657
668
|
|
|
@@ -685,9 +696,9 @@ range
|
|
|
685
696
|
.fork(branch => {
|
|
686
697
|
branch
|
|
687
698
|
.map(s => `Loading: ${s}`)
|
|
688
|
-
.
|
|
699
|
+
.apply(s => document.title = s)
|
|
689
700
|
})
|
|
690
|
-
.
|
|
701
|
+
.apply(v => progressBar.style.width = v);
|
|
691
702
|
```
|
|
692
703
|
|
|
693
704
|
|
package/internal/emitters.d.ts
CHANGED
|
@@ -16,11 +16,17 @@ export declare class Emitter<T> {
|
|
|
16
16
|
*/
|
|
17
17
|
protected redirect: (listen: ListenFunc<T>) => Emitter<T>;
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
19
|
+
* Compatibility alias for `apply()` - registers a function to receive emitted values
|
|
20
20
|
* @param handler
|
|
21
21
|
* @returns A function to deregister the handler
|
|
22
22
|
*/
|
|
23
23
|
listen(handler: Handler<T>): UnsubscribeFunc;
|
|
24
|
+
/**
|
|
25
|
+
* Registers a function to receive emitted values
|
|
26
|
+
* @param handler
|
|
27
|
+
* @returns A function to deregister the handler
|
|
28
|
+
*/
|
|
29
|
+
apply(handler: Handler<T>): UnsubscribeFunc;
|
|
24
30
|
/**
|
|
25
31
|
* Creates a chainable emitter that applies arbitrary transformation to values emitted by its parent
|
|
26
32
|
* @param mapFunc
|
|
@@ -65,9 +71,9 @@ export declare class Emitter<T> {
|
|
|
65
71
|
* .fork(branch => {
|
|
66
72
|
* branch
|
|
67
73
|
* .map(s => `Loading: ${s}`)
|
|
68
|
-
* .
|
|
74
|
+
* .apply(s => document.title = s)
|
|
69
75
|
* })
|
|
70
|
-
* .
|
|
76
|
+
* .apply(v => progressBar.style.width = v);
|
|
71
77
|
* ```
|
|
72
78
|
* @param cb
|
|
73
79
|
*/
|
|
@@ -106,7 +112,7 @@ export declare class RangeProgression extends Emitter<number> {
|
|
|
106
112
|
* ```ts
|
|
107
113
|
* range
|
|
108
114
|
* .tween("0px 0px 0px #0000", "4px 4px 8px #0005")
|
|
109
|
-
* .
|
|
115
|
+
* .apply(s => element.style.textShadow = s);
|
|
110
116
|
* ```
|
|
111
117
|
*
|
|
112
118
|
* @param from Value to interpolate from
|
|
@@ -125,6 +131,20 @@ export declare class RangeProgression extends Emitter<number> {
|
|
|
125
131
|
*/
|
|
126
132
|
tween<T extends Tweenable>(from: T, to: T): Emitter<T>;
|
|
127
133
|
tween<T extends BlendableWith<T, R>, R>(from: T, to: R): Emitter<T>;
|
|
134
|
+
/**
|
|
135
|
+
* Creates a chainable emitter that takes a value from an array according to progression
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```ts
|
|
139
|
+
* range
|
|
140
|
+
* .sample(["a", "b", "c"])
|
|
141
|
+
* .apply(v => console.log(v));
|
|
142
|
+
* // logs 'b' when a seek lands halfway through range
|
|
143
|
+
* ```
|
|
144
|
+
* @param source array to sample
|
|
145
|
+
* @returns Listenable: emits the sampled values
|
|
146
|
+
*/
|
|
147
|
+
sample<T>(source: ArrayLike<T>): Emitter<T>;
|
|
128
148
|
/**
|
|
129
149
|
* Creates a chainable progress emitter that quantises progress, as emitted by its parent, to the nearest of `steps` discrete values.
|
|
130
150
|
*
|
package/internal/emitters.js
CHANGED
|
@@ -15,7 +15,7 @@ export class Emitter {
|
|
|
15
15
|
this.redirect = (listen) => new Emitter(listen);
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
18
|
+
* Compatibility alias for `apply()` - registers a function to receive emitted values
|
|
19
19
|
* @param handler
|
|
20
20
|
* @returns A function to deregister the handler
|
|
21
21
|
*/
|
|
@@ -24,6 +24,16 @@ export class Emitter {
|
|
|
24
24
|
handler(value);
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Registers a function to receive emitted values
|
|
29
|
+
* @param handler
|
|
30
|
+
* @returns A function to deregister the handler
|
|
31
|
+
*/
|
|
32
|
+
apply(handler) {
|
|
33
|
+
return this.onListen((value) => {
|
|
34
|
+
handler(value);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
27
37
|
/**
|
|
28
38
|
* Creates a chainable emitter that applies arbitrary transformation to values emitted by its parent
|
|
29
39
|
* @param mapFunc
|
|
@@ -111,9 +121,9 @@ export class Emitter {
|
|
|
111
121
|
* .fork(branch => {
|
|
112
122
|
* branch
|
|
113
123
|
* .map(s => `Loading: ${s}`)
|
|
114
|
-
* .
|
|
124
|
+
* .apply(s => document.title = s)
|
|
115
125
|
* })
|
|
116
|
-
* .
|
|
126
|
+
* .apply(v => progressBar.style.width = v);
|
|
117
127
|
* ```
|
|
118
128
|
* @param cb
|
|
119
129
|
*/
|
|
@@ -141,6 +151,26 @@ export class RangeProgression extends Emitter {
|
|
|
141
151
|
const tween = createTween(from, to);
|
|
142
152
|
return new Emitter(handler => this.onListen(progress => handler(tween(progress))));
|
|
143
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* Creates a chainable emitter that takes a value from an array according to progression
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```ts
|
|
159
|
+
* range
|
|
160
|
+
* .sample(["a", "b", "c"])
|
|
161
|
+
* .apply(v => console.log(v));
|
|
162
|
+
* // logs 'b' when a seek lands halfway through range
|
|
163
|
+
* ```
|
|
164
|
+
* @param source array to sample
|
|
165
|
+
* @returns Listenable: emits the sampled values
|
|
166
|
+
*/
|
|
167
|
+
sample(source) {
|
|
168
|
+
return new Emitter(handler => this.onListen(progress => {
|
|
169
|
+
const clampedProgress = clamp(progress);
|
|
170
|
+
const index = Math.floor(clampedProgress * (source.length - 1));
|
|
171
|
+
handler(source[index]);
|
|
172
|
+
}));
|
|
173
|
+
}
|
|
144
174
|
/**
|
|
145
175
|
* Creates a chainable progress emitter that quantises progress, as emitted by its parent, to the nearest of `steps` discrete values.
|
|
146
176
|
*
|
|
@@ -154,7 +184,7 @@ export class RangeProgression extends Emitter {
|
|
|
154
184
|
}
|
|
155
185
|
return new RangeProgression(handler => this.onListen(progress => {
|
|
156
186
|
const snapped = Math.round(progress * steps) / steps;
|
|
157
|
-
handler(
|
|
187
|
+
handler(snapped);
|
|
158
188
|
}));
|
|
159
189
|
}
|
|
160
190
|
/**
|
package/internal/point.d.ts
CHANGED
|
@@ -36,6 +36,9 @@ export declare class TimelinePoint extends Emitter<PointEvent> {
|
|
|
36
36
|
* @returns Listenable: emits a PointEvent when the point is reached or passed by a Timeline seek
|
|
37
37
|
*/
|
|
38
38
|
delta(timeOffset: number): TimelinePoint;
|
|
39
|
+
/**
|
|
40
|
+
* Seeks the parent Timeline to this point
|
|
41
|
+
*/
|
|
39
42
|
seek(): void;
|
|
40
|
-
seek(duration
|
|
43
|
+
seek(duration: number, easer?: Easer): Promise<void>;
|
|
41
44
|
}
|
package/internal/point.js
CHANGED
|
@@ -39,6 +39,6 @@ export class TimelinePoint extends Emitter {
|
|
|
39
39
|
return this.timeline.point(this.position + timeOffset);
|
|
40
40
|
}
|
|
41
41
|
seek(duration = 0, easer) {
|
|
42
|
-
this.timeline.seek(this.position, duration, easer);
|
|
42
|
+
return this.timeline.seek(this.position, duration, easer);
|
|
43
43
|
}
|
|
44
44
|
}
|
package/internal/range.d.ts
CHANGED
|
@@ -32,8 +32,22 @@ export declare class TimelineRange extends RangeProgression {
|
|
|
32
32
|
*/
|
|
33
33
|
spread(count: number): TimelinePoint[];
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
35
|
+
* Creates the specified number of ranges, each of `(parent.duration / count)` duration, spread
|
|
36
|
+
* evenly over this range
|
|
37
|
+
* @param count Number of sub-ranges to create
|
|
38
|
+
* @returns Array of sub-ranges
|
|
39
|
+
*/
|
|
40
|
+
subdivide(count: number): TimelineRange[];
|
|
41
|
+
/**
|
|
42
|
+
* Creates a new range by offsetting the parent by a given time delta
|
|
43
|
+
* @param delta
|
|
44
|
+
* @returns Offset range
|
|
45
|
+
*/
|
|
46
|
+
shift(delta: number): TimelineRange;
|
|
47
|
+
/**
|
|
48
|
+
* Progresses the Timeline across the range at 1000 units per second
|
|
49
|
+
* @param easer Optional easing function
|
|
50
|
+
* @returns Promise, resolved when the end is reached
|
|
37
51
|
*/
|
|
38
52
|
play(easer?: Easer | keyof typeof easers): Promise<void>;
|
|
39
53
|
/**
|
package/internal/range.js
CHANGED
|
@@ -44,8 +44,27 @@ export class TimelineRange extends RangeProgression {
|
|
|
44
44
|
];
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
47
|
+
* Creates the specified number of ranges, each of `(parent.duration / count)` duration, spread
|
|
48
|
+
* evenly over this range
|
|
49
|
+
* @param count Number of sub-ranges to create
|
|
50
|
+
* @returns Array of sub-ranges
|
|
51
|
+
*/
|
|
52
|
+
subdivide(count) {
|
|
53
|
+
const duration = this.duration / count;
|
|
54
|
+
return Array.from({ length: count }, (_, i) => this.timeline.range(this.startPosition + i * duration, duration));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Creates a new range by offsetting the parent by a given time delta
|
|
58
|
+
* @param delta
|
|
59
|
+
* @returns Offset range
|
|
60
|
+
*/
|
|
61
|
+
shift(delta) {
|
|
62
|
+
return this.timeline.range(this.startPosition + delta, this.duration);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Progresses the Timeline across the range at 1000 units per second
|
|
66
|
+
* @param easer Optional easing function
|
|
67
|
+
* @returns Promise, resolved when the end is reached
|
|
49
68
|
*/
|
|
50
69
|
play(easer) {
|
|
51
70
|
this.timeline.pause();
|
|
@@ -78,7 +97,7 @@ export class TimelineRange extends RangeProgression {
|
|
|
78
97
|
*/
|
|
79
98
|
scale(factor, anchor = 0) {
|
|
80
99
|
if (factor <= 0) {
|
|
81
|
-
throw new RangeError('
|
|
100
|
+
throw new RangeError('Scale factor must be > 0');
|
|
82
101
|
}
|
|
83
102
|
const clampedAnchor = clamp(anchor, 0, 1);
|
|
84
103
|
const oldLen = this.endPosition - this.startPosition;
|
package/internal/timeline.js
CHANGED
|
@@ -184,7 +184,7 @@ export class Timeline {
|
|
|
184
184
|
if (this.smoothSeeker !== null) {
|
|
185
185
|
this.smoothSeeker.pause();
|
|
186
186
|
// ensure any awaits are resolved for the previous seek
|
|
187
|
-
this.smoothSeeker.seek(
|
|
187
|
+
this.smoothSeeker.end.seek();
|
|
188
188
|
this.smoothSeeker = null;
|
|
189
189
|
}
|
|
190
190
|
if (duration === 0) {
|
|
@@ -193,8 +193,8 @@ export class Timeline {
|
|
|
193
193
|
}
|
|
194
194
|
const seeker = new Timeline(true);
|
|
195
195
|
this.smoothSeeker = seeker;
|
|
196
|
-
seeker.range(0, duration).ease(easer).tween(this.currentTime, toPosition).
|
|
197
|
-
return new Promise(r => seeker.end.
|
|
196
|
+
seeker.range(0, duration).ease(easer).tween(this.currentTime, toPosition).apply(v => this.seekDirect(v));
|
|
197
|
+
return new Promise(r => seeker.end.apply(() => r()));
|
|
198
198
|
}
|
|
199
199
|
seekDirect(toPosition) {
|
|
200
200
|
const fromPosition = this._currentTime;
|
|
@@ -321,7 +321,7 @@ export class Timeline {
|
|
|
321
321
|
const duration = typeof durationOrToPoint == "number"
|
|
322
322
|
? durationOrToPoint
|
|
323
323
|
: (durationOrToPoint.position - startPosition);
|
|
324
|
-
this.range(startPosition, duration).ease(easer).tween(from, to).
|
|
324
|
+
this.range(startPosition, duration).ease(easer).tween(from, to).apply(apply);
|
|
325
325
|
return this.createChainingInterface(startPosition + duration);
|
|
326
326
|
}
|
|
327
327
|
at(position, action, reverse) {
|
|
@@ -329,7 +329,7 @@ export class Timeline {
|
|
|
329
329
|
if (reverse === true)
|
|
330
330
|
reverse = action;
|
|
331
331
|
if (action)
|
|
332
|
-
point.
|
|
332
|
+
point.apply(reverse
|
|
333
333
|
? (event => event.direction < 0 ? reverse() : action)
|
|
334
334
|
: action);
|
|
335
335
|
return this.createChainingInterface(point.position);
|
package/internal/tween.d.ts
CHANGED
|
@@ -7,5 +7,7 @@ export interface Blendable {
|
|
|
7
7
|
export interface BlendableWith<T, R> {
|
|
8
8
|
blend(target: R, progress: number): T;
|
|
9
9
|
}
|
|
10
|
-
|
|
11
|
-
export declare function createTween<T extends
|
|
10
|
+
type TweenFunc<T> = (progress: number) => T;
|
|
11
|
+
export declare function createTween<T extends Tweenable>(from: T, to: T): TweenFunc<T>;
|
|
12
|
+
export declare function createTween<T extends BlendableWith<T, R>, R>(from: T, to: R): TweenFunc<T>;
|
|
13
|
+
export {};
|
package/internal/tween.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { clamp } from "./utils";
|
|
2
|
+
const tokenTypes = {
|
|
3
|
+
none: 0,
|
|
4
|
+
number: 1,
|
|
5
|
+
colour: 2,
|
|
6
|
+
};
|
|
2
7
|
export function createTween(from, to) {
|
|
3
8
|
if (from === to)
|
|
4
9
|
return () => from;
|
|
@@ -24,8 +29,9 @@ function createStringTween(from, to) {
|
|
|
24
29
|
if (tokenCount !== toChunks.filter(c => c.token).length) {
|
|
25
30
|
return createStringMerge(from, to);
|
|
26
31
|
}
|
|
27
|
-
// where token prefix mismatch, use merging
|
|
28
|
-
if (fromChunks.some((chunk, i) => toChunks[i].prefix !== chunk.prefix
|
|
32
|
+
// where token prefix/type mismatch, use merging
|
|
33
|
+
if (fromChunks.some((chunk, i) => toChunks[i].prefix !== chunk.prefix ||
|
|
34
|
+
toChunks[i].type !== chunk.type)) {
|
|
29
35
|
return createStringMerge(from, to);
|
|
30
36
|
}
|
|
31
37
|
// convert token chunks to individual string tween funcs
|
|
@@ -33,9 +39,9 @@ function createStringTween(from, to) {
|
|
|
33
39
|
const fromToken = chunk.token;
|
|
34
40
|
const toToken = toChunks[i].token;
|
|
35
41
|
const prefix = chunk.prefix;
|
|
36
|
-
if (
|
|
42
|
+
if (chunk.type === tokenTypes.none)
|
|
37
43
|
return () => prefix;
|
|
38
|
-
if (
|
|
44
|
+
if (chunk.type === tokenTypes.colour) {
|
|
39
45
|
const fromColour = parseColour(fromToken);
|
|
40
46
|
const toColour = parseColour(toToken);
|
|
41
47
|
return progress => prefix + blendColours(fromColour, toColour, progress);
|
|
@@ -65,7 +71,7 @@ function createStringMerge(from, to) {
|
|
|
65
71
|
if (!to)
|
|
66
72
|
return () => from;
|
|
67
73
|
const split = (s) => {
|
|
68
|
-
// prefer Intl.Segmenter if available (Node
|
|
74
|
+
// prefer Intl.Segmenter if available (Node 14, modern browsers)
|
|
69
75
|
if (typeof Intl !== "undefined" && Intl.Segmenter) {
|
|
70
76
|
const seg = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
71
77
|
return Array.from(seg.segment(s), (seg) => seg.segment);
|
|
@@ -86,7 +92,7 @@ function createStringMerge(from, to) {
|
|
|
86
92
|
const fromP = pad(a);
|
|
87
93
|
const toP = pad(b);
|
|
88
94
|
return (progress) => {
|
|
89
|
-
const clampedProgress = clamp(progress
|
|
95
|
+
const clampedProgress = clamp(progress);
|
|
90
96
|
const replaceCount = Math.floor(clampedProgress * maxLen);
|
|
91
97
|
const result = new Array(maxLen);
|
|
92
98
|
for (let i = 0; i < maxLen; ++i) {
|
|
@@ -122,20 +128,27 @@ function blendColours(from, to, bias) {
|
|
|
122
128
|
return ("#" + blended.map(n => Math.round(n).toString(16).padStart(2, "0")).join("")).replace(/ff$/, "");
|
|
123
129
|
}
|
|
124
130
|
const tweenableTokenRegex = /(#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\b|[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)/g;
|
|
125
|
-
|
|
131
|
+
function tokenise(s) {
|
|
126
132
|
const chunks = [];
|
|
127
133
|
let lastIdx = 0;
|
|
128
134
|
let m;
|
|
129
135
|
while ((m = tweenableTokenRegex.exec(s))) {
|
|
130
136
|
const token = m[0];
|
|
131
137
|
const prefix = s.slice(lastIdx, m.index); // literal before token
|
|
132
|
-
|
|
138
|
+
const type = getTokenType(token);
|
|
139
|
+
chunks.push({ prefix, token, type });
|
|
133
140
|
lastIdx = m.index + token.length;
|
|
134
141
|
}
|
|
135
142
|
// trailing literal after the last token – stored as a final chunk
|
|
136
143
|
const tail = s.slice(lastIdx);
|
|
137
144
|
if (tail.length) {
|
|
138
|
-
chunks.push({ prefix: tail, token: "" });
|
|
145
|
+
chunks.push({ prefix: tail, token: "", type: tokenTypes.none });
|
|
139
146
|
}
|
|
140
147
|
return chunks;
|
|
141
|
-
}
|
|
148
|
+
}
|
|
149
|
+
;
|
|
150
|
+
function getTokenType(token) {
|
|
151
|
+
if (token.startsWith("#"))
|
|
152
|
+
return tokenTypes.colour;
|
|
153
|
+
return tokenTypes.number;
|
|
154
|
+
}
|
package/internal/utils.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** @internal */
|
|
2
|
-
export declare const clamp: (value: number, min
|
|
2
|
+
export declare const clamp: (value: number, min?: number, max?: number) => number;
|
|
3
3
|
/** @internal */
|
|
4
4
|
export type Widen<T> = T extends number ? number : T extends string ? string : T;
|
package/internal/utils.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** @internal */
|
|
2
|
-
export const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
2
|
+
export const clamp = (value, min = 0, max = 1) => Math.min(Math.max(value, min), max);
|