@xtia/timeline 1.0.12 → 1.1.1
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 +88 -41
- package/internal/emitters.d.ts +11 -5
- package/internal/emitters.js +15 -5
- package/internal/point.d.ts +39 -1
- package/internal/point.js +63 -0
- package/internal/range.d.ts +16 -2
- package/internal/range.js +22 -3
- package/internal/timeline.js +5 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Timeline
|
|
2
2
|
|
|
3
|
-
###
|
|
3
|
+
### Not Just Another Animation Library
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Timeline is a type-safe, deterministic choreography system that can control state transitions in any environment, whether that's a simple or complex CSS animation, managing a microcontroller's output, or synchronising complex hardware sequences.
|
|
6
6
|
|
|
7
7
|
* [API Reference](#reference)
|
|
8
8
|
* [Playground](https://stackblitz.com/edit/timeline-string-tween?file=src%2Fmain.ts)
|
|
@@ -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
|
|
|
@@ -29,20 +29,18 @@ timeline
|
|
|
29
29
|
const message = "Hi, planet!";
|
|
30
30
|
timeline
|
|
31
31
|
.range(500, 2000)
|
|
32
|
+
.ease("easeOut")
|
|
32
33
|
.tween(0, message.length)
|
|
33
34
|
.map(n => message.substring(0, n))
|
|
34
|
-
.
|
|
35
|
+
.apply(
|
|
35
36
|
s => element.textContent = s
|
|
36
37
|
);
|
|
37
38
|
|
|
38
|
-
//
|
|
39
|
+
// control anything:
|
|
39
40
|
timeline
|
|
40
|
-
.range(
|
|
41
|
-
.
|
|
42
|
-
.
|
|
43
|
-
.listen(
|
|
44
|
-
value => element.style.marginLeft = value
|
|
45
|
-
);
|
|
41
|
+
.range(1000, 2000)
|
|
42
|
+
.tween(0, 255)
|
|
43
|
+
.listen(value => microcontroller.setPWM(value))
|
|
46
44
|
|
|
47
45
|
// make it go
|
|
48
46
|
timeline.play();
|
|
@@ -56,11 +54,11 @@ timeline.play();
|
|
|
56
54
|
const firstFiveSeconds = timeline.range(0, 5000);
|
|
57
55
|
```
|
|
58
56
|
|
|
59
|
-
The range object is *
|
|
57
|
+
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
58
|
|
|
61
59
|
```ts
|
|
62
60
|
firstFiveSeconds
|
|
63
|
-
.
|
|
61
|
+
.apply(
|
|
64
62
|
value => console.log(`${value} is between 0 and 1`)
|
|
65
63
|
);
|
|
66
64
|
```
|
|
@@ -74,20 +72,20 @@ const asPercent = firstFiveSeconds.map(n => n * 100);
|
|
|
74
72
|
// use the result in a log message
|
|
75
73
|
asPercent
|
|
76
74
|
.map(n => n.toFixed(2))
|
|
77
|
-
.
|
|
75
|
+
.apply(
|
|
78
76
|
n => console.log(`We are ${n}% through the first five seconds`)
|
|
79
77
|
);
|
|
80
78
|
|
|
81
79
|
// and in a css property
|
|
82
80
|
asPercent
|
|
83
81
|
.map(n => `${n}%`)
|
|
84
|
-
.
|
|
82
|
+
.apply(
|
|
85
83
|
n => progressBar.style.width = n
|
|
86
84
|
);
|
|
87
85
|
|
|
88
86
|
// apply easing
|
|
89
87
|
const eased = firstFiveSeconds.ease("easeInOut");
|
|
90
|
-
eased.
|
|
88
|
+
eased.apply(
|
|
91
89
|
v => console.log(`Eased value: ${v}`)
|
|
92
90
|
);
|
|
93
91
|
|
|
@@ -98,7 +96,7 @@ range
|
|
|
98
96
|
.dedupe()
|
|
99
97
|
.tap(n => console.log("Showing frame #", n))
|
|
100
98
|
.map(n => `animation-frame-${n}.png`)
|
|
101
|
-
.
|
|
99
|
+
.apply(filename => img.src = filename);
|
|
102
100
|
|
|
103
101
|
// each step in a chain is a 'pure', independent emitter that emits a
|
|
104
102
|
// transformation of its parent's emissions
|
|
@@ -129,7 +127,7 @@ Custom easers can be passed to `ease()` as `(progress: number) => number`:
|
|
|
129
127
|
```ts
|
|
130
128
|
timeline
|
|
131
129
|
.range(0, 1000)
|
|
132
|
-
.ease(n =>
|
|
130
|
+
.ease(n => n * n)
|
|
133
131
|
.tween(/*...*/);
|
|
134
132
|
```
|
|
135
133
|
|
|
@@ -146,7 +144,7 @@ const sixSecondsIn = fiveSecondsdIn.delta(1000);
|
|
|
146
144
|
Points emit `PointEvent` objects when their position is reached or passed.
|
|
147
145
|
|
|
148
146
|
```ts
|
|
149
|
-
twoSecondsIn.
|
|
147
|
+
twoSecondsIn.apply(event => {
|
|
150
148
|
// event.direction (-1 | 1) tells us the direction of the seek that
|
|
151
149
|
// triggered the point. This allows for reversible point events
|
|
152
150
|
document.body.classList.toggle("someClass", event.direction > 0);
|
|
@@ -181,30 +179,30 @@ const range = timeline.range(0, 2000);
|
|
|
181
179
|
range
|
|
182
180
|
.ease("overshootIn")
|
|
183
181
|
.tween(300, 500)
|
|
184
|
-
.
|
|
182
|
+
.apply(v => element.scrollTop = v);
|
|
185
183
|
|
|
186
184
|
// number arrays
|
|
187
185
|
range
|
|
188
186
|
.tween([0, 180], [360, 180])
|
|
189
|
-
.
|
|
187
|
+
.apply((angles) => pieChart.setValues(angles));
|
|
190
188
|
|
|
191
189
|
// strings
|
|
192
190
|
range
|
|
193
191
|
.tween("#000000", "#ff00ff")
|
|
194
|
-
.
|
|
192
|
+
.apply(v => element.style.color = v);
|
|
195
193
|
|
|
196
194
|
// blendable objects
|
|
197
195
|
// (T extends { blend(from: this, to: this): this })
|
|
198
196
|
import { RGBA } from "@xtia/rgba";
|
|
199
197
|
range
|
|
200
198
|
.tween(RGBA.parse("#c971a7"), RGBA.parse("#fff"))
|
|
201
|
-
.
|
|
199
|
+
.apply(v => element.style.background = v);
|
|
202
200
|
|
|
203
201
|
import { Angle } from "@xtia/mezr";
|
|
204
202
|
range
|
|
205
203
|
.tween(Angle.degrees(45), Angle.turns(.5))
|
|
206
204
|
.map(a => `rotate(${a.asDegrees}deg)`)
|
|
207
|
-
.
|
|
205
|
+
.apply(v => element.style.transform = v);
|
|
208
206
|
|
|
209
207
|
```
|
|
210
208
|
|
|
@@ -218,14 +216,14 @@ timeline
|
|
|
218
216
|
.range(0, 2000)
|
|
219
217
|
.ease("elastic")
|
|
220
218
|
.tween("0px 0px 0px #0000", "15px 15px 20px #0005")
|
|
221
|
-
.
|
|
219
|
+
.apply(s => element.style.textShadow = s);
|
|
222
220
|
|
|
223
221
|
// text progress bar
|
|
224
222
|
timeline
|
|
225
223
|
.range(0, 2000)
|
|
226
224
|
.tween("--------", "########")
|
|
227
225
|
.dedupe()
|
|
228
|
-
.
|
|
226
|
+
.apply(v => document.title = v);
|
|
229
227
|
```
|
|
230
228
|
|
|
231
229
|
Try out the [shadow tweening example at StackBlitz](https://stackblitz.com/edit/timeline-string-tween?file=src%2Fmain.ts)
|
|
@@ -238,13 +236,13 @@ To create a Timeline that immediately starts playing, pass `true` to its constru
|
|
|
238
236
|
// immediately fade in an element
|
|
239
237
|
new Timeline(true)
|
|
240
238
|
.range(0, 1000)
|
|
241
|
-
.
|
|
239
|
+
.apply(v => element.style.opacity = v);
|
|
242
240
|
|
|
243
241
|
// note, an `animate(duration)` function is exported for
|
|
244
242
|
// disposable, single-use animations such as this:
|
|
245
243
|
import { animate } from "@xtia/timeline";
|
|
246
244
|
animate(1000)
|
|
247
|
-
.
|
|
245
|
+
.apply(v => element.style.opacity = v);
|
|
248
246
|
```
|
|
249
247
|
|
|
250
248
|
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 +300,19 @@ window.addEventListener(
|
|
|
302
300
|
setInterval(() => timeline.seek(Date.now()), 1000);
|
|
303
301
|
timeline
|
|
304
302
|
.point(new Date("2026-10-31").getTime())
|
|
305
|
-
.
|
|
303
|
+
.apply(() => console.log("Happy anniversary 🏳️⚧️💗"));
|
|
306
304
|
|
|
307
305
|
// show a progress bar for loaded resources
|
|
308
306
|
const loadingTimeline = new Timeline();
|
|
309
307
|
loadingTimeline
|
|
310
308
|
.range(0, resourceUrls.length)
|
|
311
309
|
.tween("0%", "100%");
|
|
312
|
-
.
|
|
310
|
+
.apply(v => progressBar.style.width = v);
|
|
313
311
|
|
|
314
312
|
// and do something when they're loaded
|
|
315
313
|
loadingTimeline
|
|
316
314
|
.end
|
|
317
|
-
.
|
|
315
|
+
.apply(startGame);
|
|
318
316
|
|
|
319
317
|
// to drive it, just seek forward by 1 for each loaded resource
|
|
320
318
|
resourceUrls.forEach(url => {
|
|
@@ -435,7 +433,7 @@ timeline
|
|
|
435
433
|
.range(start, duration)
|
|
436
434
|
.ease(easer)
|
|
437
435
|
.tween(from, to)
|
|
438
|
-
.
|
|
436
|
+
.apply(apply);
|
|
439
437
|
```
|
|
440
438
|
|
|
441
439
|
Returns a [`ChainingInterface`](#chaininginterface-interface) representing the point at which the tween ends.
|
|
@@ -491,6 +489,33 @@ Seeks the parent Timeline to this point.
|
|
|
491
489
|
|
|
492
490
|
Smooth-seeks the parent Timeline to this point over a specified duration and resolves the returned Promise on completion.
|
|
493
491
|
|
|
492
|
+
##### `promise(): Promise<-1 | 1>`
|
|
493
|
+
|
|
494
|
+
Creates a `Promise` that will be resolved when the Timeline first seeks to/past this point.
|
|
495
|
+
|
|
496
|
+
The resolved value indicates the direction of the seek that triggered resolution.
|
|
497
|
+
|
|
498
|
+
##### `forwardOnly(): Emitter<PointEvent>`
|
|
499
|
+
|
|
500
|
+
Creates an emitter that forwards emissions triggered by forward-moving seeks.
|
|
501
|
+
|
|
502
|
+
##### `reverseOnly(): Emitter<PointEvent>`
|
|
503
|
+
|
|
504
|
+
Creates an emitter that forwards emissions triggered by backward-moving seeks.
|
|
505
|
+
|
|
506
|
+
##### `applyDirectional(apply, revert): UnsubscribeFunc`
|
|
507
|
+
|
|
508
|
+
Registers an emission handler that calls one function for forward seeks to or past the point, and another for backward seeks from or past the point.
|
|
509
|
+
|
|
510
|
+
```ts
|
|
511
|
+
point
|
|
512
|
+
.applyDirectional(
|
|
513
|
+
element.classList.add("faded"),
|
|
514
|
+
element.classList.remove("faded"),
|
|
515
|
+
);
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
|
|
494
519
|
|
|
495
520
|
|
|
496
521
|
### `PointEvent` interface
|
|
@@ -508,7 +533,7 @@ Allows point listeners to undo effects when the Timeline is reversed.
|
|
|
508
533
|
```ts
|
|
509
534
|
timeline
|
|
510
535
|
.point(4000)
|
|
511
|
-
.
|
|
536
|
+
.apply(
|
|
512
537
|
event => element.classList.toggle(
|
|
513
538
|
"visible",
|
|
514
539
|
event.direction > 0
|
|
@@ -565,10 +590,22 @@ Creates a new range on the parent Timeline. The location and duration of the new
|
|
|
565
590
|
|
|
566
591
|
Creates a new range on the parent Timeline. The location and duration of the new range are copied from this range and scaled multiplicatively from an anchor point, specified as a normalised (0..1) progression of the parent range.
|
|
567
592
|
|
|
568
|
-
##### `
|
|
593
|
+
##### `subdivide(n): TimelineRange[]`
|
|
594
|
+
|
|
595
|
+
Creates the specified number of ranges, each of `(parent.duration / count)` duration, spread evenly over this range.
|
|
596
|
+
|
|
597
|
+
##### `shift(delta): TimelineRange`
|
|
598
|
+
|
|
599
|
+
Creates a new range by offsetting the parent by a given time delta.
|
|
600
|
+
|
|
601
|
+
##### `contains(point): boolean`
|
|
569
602
|
|
|
570
603
|
Returns true if the given [`TimelinePoint`](#timelinepoint-interface) sits within this range.
|
|
571
604
|
|
|
605
|
+
##### `overlaps(range): boolean`
|
|
606
|
+
|
|
607
|
+
Returns true if the given range overlaps with this range.
|
|
608
|
+
|
|
572
609
|
|
|
573
610
|
|
|
574
611
|
|
|
@@ -634,9 +671,20 @@ If `check(value)` returns true, the value will be emitted.
|
|
|
634
671
|
|
|
635
672
|
Creates an emitter that discards emitted values that are the same as the last value emitted by the new emitter
|
|
636
673
|
|
|
674
|
+
##### `sample<T>(items): T`
|
|
675
|
+
|
|
676
|
+
Creates a chainable emitter that takes a value from an array according to progression.
|
|
677
|
+
|
|
678
|
+
```ts
|
|
679
|
+
range
|
|
680
|
+
.sample(["a", "b", "c"])
|
|
681
|
+
.apply(v => console.log(v));
|
|
682
|
+
// logs 'b' when a seek lands halfway through range
|
|
683
|
+
```
|
|
684
|
+
|
|
637
685
|
##### `offset(delta): RangeProgression`
|
|
638
686
|
|
|
639
|
-
Creates an emitter that offsets its parent's values by the given delta, wrapping at 1
|
|
687
|
+
Creates an emitter that offsets its parent's values by the given delta, wrapping at 1.
|
|
640
688
|
|
|
641
689
|
##### `fork(cb: (branch) => void): RangeProgression`
|
|
642
690
|
|
|
@@ -650,19 +698,18 @@ range
|
|
|
650
698
|
.fork(branch => {
|
|
651
699
|
branch
|
|
652
700
|
.map(s => `Loading: ${s}`)
|
|
653
|
-
.
|
|
701
|
+
.apply(s => document.title = s)
|
|
654
702
|
})
|
|
655
|
-
.
|
|
703
|
+
.apply(v => progressBar.style.width = v);
|
|
656
704
|
```
|
|
657
705
|
|
|
658
706
|
|
|
659
707
|
|
|
660
|
-
|
|
661
708
|
### `Emitter<T>` interface
|
|
662
709
|
|
|
663
710
|
#### Methods
|
|
664
711
|
|
|
665
|
-
##### `
|
|
712
|
+
##### `apply(handler: Handler<T>): UnsubscribeFunc`
|
|
666
713
|
|
|
667
714
|
Attaches a handler to the emitter and returns a function that will unsubscribe the handler.
|
|
668
715
|
|
|
@@ -696,9 +743,9 @@ range
|
|
|
696
743
|
.fork(branch => {
|
|
697
744
|
branch
|
|
698
745
|
.map(s => `Loading: ${s}`)
|
|
699
|
-
.
|
|
746
|
+
.apply(s => document.title = s)
|
|
700
747
|
})
|
|
701
|
-
.
|
|
748
|
+
.apply(v => progressBar.style.width = v);
|
|
702
749
|
```
|
|
703
750
|
|
|
704
751
|
|
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
|
|
@@ -132,7 +138,7 @@ export declare class RangeProgression extends Emitter<number> {
|
|
|
132
138
|
* ```ts
|
|
133
139
|
* range
|
|
134
140
|
* .sample(["a", "b", "c"])
|
|
135
|
-
* .
|
|
141
|
+
* .apply(v => console.log(v));
|
|
136
142
|
* // logs 'b' when a seek lands halfway through range
|
|
137
143
|
* ```
|
|
138
144
|
* @param source array to sample
|
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
|
*/
|
|
@@ -148,7 +158,7 @@ export class RangeProgression extends Emitter {
|
|
|
148
158
|
* ```ts
|
|
149
159
|
* range
|
|
150
160
|
* .sample(["a", "b", "c"])
|
|
151
|
-
* .
|
|
161
|
+
* .apply(v => console.log(v));
|
|
152
162
|
* // logs 'b' when a seek lands halfway through range
|
|
153
163
|
* ```
|
|
154
164
|
* @param source array to sample
|
|
@@ -174,7 +184,7 @@ export class RangeProgression extends Emitter {
|
|
|
174
184
|
}
|
|
175
185
|
return new RangeProgression(handler => this.onListen(progress => {
|
|
176
186
|
const snapped = Math.round(progress * steps) / steps;
|
|
177
|
-
handler(
|
|
187
|
+
handler(snapped);
|
|
178
188
|
}));
|
|
179
189
|
}
|
|
180
190
|
/**
|
package/internal/point.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Easer } from "./easing";
|
|
2
|
-
import { Emitter, ListenFunc } from "./emitters";
|
|
2
|
+
import { Emitter, ListenFunc, UnsubscribeFunc } from "./emitters";
|
|
3
3
|
import { TimelineRange } from "./range";
|
|
4
4
|
import { Timeline } from "./timeline";
|
|
5
5
|
export type PointEvent = {
|
|
@@ -41,4 +41,42 @@ export declare class TimelinePoint extends Emitter<PointEvent> {
|
|
|
41
41
|
*/
|
|
42
42
|
seek(): void;
|
|
43
43
|
seek(duration: number, easer?: Easer): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Creates an emitter that only emits on forward-moving seeks
|
|
46
|
+
* @returns
|
|
47
|
+
*/
|
|
48
|
+
forwardOnly(): Emitter<PointEvent>;
|
|
49
|
+
/**
|
|
50
|
+
* Creates an emitter that only emits on backward-moving seeks
|
|
51
|
+
* @returns
|
|
52
|
+
*/
|
|
53
|
+
reverseOnly(): Emitter<PointEvent>;
|
|
54
|
+
/**
|
|
55
|
+
* Creates a Promise that will be resolved when the Timeline first seeks to/past this point
|
|
56
|
+
*
|
|
57
|
+
* The resolved value indicates the direction of the seek that triggered resolution
|
|
58
|
+
*
|
|
59
|
+
* @returns A Promise, resolved when the point is triggered by a seek
|
|
60
|
+
*/
|
|
61
|
+
promise(): Promise<1 | -1>;
|
|
62
|
+
/**
|
|
63
|
+
* Registers a pair of functions to handle seeks that reach or pass this point, depending on seek direction
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```
|
|
67
|
+
* point
|
|
68
|
+
* .applyDirectional(
|
|
69
|
+
* element.classList.add("faded"),
|
|
70
|
+
* element.classList.remove("faded"),
|
|
71
|
+
* );
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* Note, for deterministic consistency, a forward-seek triggers points when it
|
|
75
|
+
* *passes or reaches* them, while a backward-seek triggers points when it
|
|
76
|
+
* *passes or departs from* them.
|
|
77
|
+
* @param apply Handler for forward-moving seeks that pass or reach this point
|
|
78
|
+
* @param revert Handler for backward-moving seeks that pass this point
|
|
79
|
+
* @returns A function to deregister both handlers
|
|
80
|
+
*/
|
|
81
|
+
applyDirectional(apply: () => void, revert: () => void): UnsubscribeFunc;
|
|
44
82
|
}
|
package/internal/point.js
CHANGED
|
@@ -41,4 +41,67 @@ export class TimelinePoint extends Emitter {
|
|
|
41
41
|
seek(duration = 0, easer) {
|
|
42
42
|
return this.timeline.seek(this.position, duration, easer);
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Creates an emitter that only emits on forward-moving seeks
|
|
46
|
+
* @returns
|
|
47
|
+
*/
|
|
48
|
+
forwardOnly() {
|
|
49
|
+
return new Emitter(handler => {
|
|
50
|
+
return this.onListen((ev) => {
|
|
51
|
+
if (ev.direction > 0)
|
|
52
|
+
handler(ev);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Creates an emitter that only emits on backward-moving seeks
|
|
58
|
+
* @returns
|
|
59
|
+
*/
|
|
60
|
+
reverseOnly() {
|
|
61
|
+
return new Emitter(handler => {
|
|
62
|
+
return this.onListen((ev) => {
|
|
63
|
+
if (ev.direction < 0)
|
|
64
|
+
handler(ev);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Creates a Promise that will be resolved when the Timeline first seeks to/past this point
|
|
70
|
+
*
|
|
71
|
+
* The resolved value indicates the direction of the seek that triggered resolution
|
|
72
|
+
*
|
|
73
|
+
* @returns A Promise, resolved when the point is triggered by a seek
|
|
74
|
+
*/
|
|
75
|
+
promise() {
|
|
76
|
+
return new Promise(resolve => {
|
|
77
|
+
let remove = this.apply((ev) => {
|
|
78
|
+
remove();
|
|
79
|
+
resolve(ev.direction);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Registers a pair of functions to handle seeks that reach or pass this point, depending on seek direction
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```
|
|
88
|
+
* point
|
|
89
|
+
* .applyDirectional(
|
|
90
|
+
* element.classList.add("faded"),
|
|
91
|
+
* element.classList.remove("faded"),
|
|
92
|
+
* );
|
|
93
|
+
* ```
|
|
94
|
+
*
|
|
95
|
+
* Note, for deterministic consistency, a forward-seek triggers points when it
|
|
96
|
+
* *passes or reaches* them, while a backward-seek triggers points when it
|
|
97
|
+
* *passes or departs from* them.
|
|
98
|
+
* @param apply Handler for forward-moving seeks that pass or reach this point
|
|
99
|
+
* @param revert Handler for backward-moving seeks that pass this point
|
|
100
|
+
* @returns A function to deregister both handlers
|
|
101
|
+
*/
|
|
102
|
+
applyDirectional(apply, revert) {
|
|
103
|
+
return this.onListen(eventData => eventData.direction > 0
|
|
104
|
+
? apply()
|
|
105
|
+
: revert());
|
|
106
|
+
}
|
|
44
107
|
}
|
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);
|