@xtia/timeline 1.1.0 → 1.1.2
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 +61 -14
- package/internal/point.d.ts +39 -1
- package/internal/point.js +63 -0
- package/internal/timeline.d.ts +1 -0
- package/internal/timeline.js +7 -2
- package/internal/tween.js +12 -10
- 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, seekable, 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)
|
|
@@ -17,7 +17,7 @@ import { Timeline } from "@xtia/timeline";
|
|
|
17
17
|
// create a Timeline
|
|
18
18
|
const timeline = new Timeline();
|
|
19
19
|
|
|
20
|
-
// over the first second, fade
|
|
20
|
+
// over the first second, fade an element's background colour
|
|
21
21
|
timeline
|
|
22
22
|
.range(0, 1000)
|
|
23
23
|
.tween("#646", "#000")
|
|
@@ -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
|
-
.apply(
|
|
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();
|
|
@@ -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
|
|
|
@@ -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
|
|
@@ -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
|
|
|
@@ -657,7 +705,6 @@ range
|
|
|
657
705
|
|
|
658
706
|
|
|
659
707
|
|
|
660
|
-
|
|
661
708
|
### `Emitter<T>` interface
|
|
662
709
|
|
|
663
710
|
#### Methods
|
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/timeline.d.ts
CHANGED
|
@@ -151,6 +151,7 @@ export interface ChainingInterface {
|
|
|
151
151
|
thenTween<T extends Tweenable>(duration: number, apply: (v: Widen<T>) => void, from: T, to: T, easer: Easer): ChainingInterface;
|
|
152
152
|
then(action: () => void): ChainingInterface;
|
|
153
153
|
thenWait(duration: number): ChainingInterface;
|
|
154
|
+
fork(fn: (chain: ChainingInterface) => void): ChainingInterface;
|
|
154
155
|
readonly end: TimelinePoint;
|
|
155
156
|
}
|
|
156
157
|
export {};
|
package/internal/timeline.js
CHANGED
|
@@ -330,12 +330,12 @@ export class Timeline {
|
|
|
330
330
|
reverse = action;
|
|
331
331
|
if (action)
|
|
332
332
|
point.apply(reverse
|
|
333
|
-
? (event => event.direction < 0 ? reverse() : action)
|
|
333
|
+
? (event => event.direction < 0 ? reverse() : action())
|
|
334
334
|
: action);
|
|
335
335
|
return this.createChainingInterface(point.position);
|
|
336
336
|
}
|
|
337
337
|
createChainingInterface(position) {
|
|
338
|
-
|
|
338
|
+
const chain = {
|
|
339
339
|
thenTween: (duration, apply, from, to, easer) => {
|
|
340
340
|
return this.tween(position, duration, apply, from, to, easer);
|
|
341
341
|
},
|
|
@@ -344,8 +344,13 @@ export class Timeline {
|
|
|
344
344
|
this.point(position + delay);
|
|
345
345
|
return this.createChainingInterface(position + delay);
|
|
346
346
|
},
|
|
347
|
+
fork: fn => {
|
|
348
|
+
fn(chain);
|
|
349
|
+
return chain;
|
|
350
|
+
},
|
|
347
351
|
end: this.point(position),
|
|
348
352
|
};
|
|
353
|
+
return chain;
|
|
349
354
|
}
|
|
350
355
|
/**
|
|
351
356
|
* @deprecated use `timeline.currentTime`
|
package/internal/tween.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { clamp } from "./utils";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
var TokenTypes;
|
|
3
|
+
(function (TokenTypes) {
|
|
4
|
+
TokenTypes[TokenTypes["none"] = 0] = "none";
|
|
5
|
+
TokenTypes[TokenTypes["number"] = 1] = "number";
|
|
6
|
+
TokenTypes[TokenTypes["colour"] = 2] = "colour";
|
|
7
|
+
})(TokenTypes || (TokenTypes = {}));
|
|
8
|
+
;
|
|
7
9
|
export function createTween(from, to) {
|
|
8
10
|
if (from === to)
|
|
9
11
|
return () => from;
|
|
@@ -39,9 +41,9 @@ function createStringTween(from, to) {
|
|
|
39
41
|
const fromToken = chunk.token;
|
|
40
42
|
const toToken = toChunks[i].token;
|
|
41
43
|
const prefix = chunk.prefix;
|
|
42
|
-
if (chunk.type ===
|
|
44
|
+
if (chunk.type === TokenTypes.none)
|
|
43
45
|
return () => prefix;
|
|
44
|
-
if (chunk.type ===
|
|
46
|
+
if (chunk.type === TokenTypes.colour) {
|
|
45
47
|
const fromColour = parseColour(fromToken);
|
|
46
48
|
const toColour = parseColour(toToken);
|
|
47
49
|
return progress => prefix + blendColours(fromColour, toColour, progress);
|
|
@@ -142,13 +144,13 @@ function tokenise(s) {
|
|
|
142
144
|
// trailing literal after the last token – stored as a final chunk
|
|
143
145
|
const tail = s.slice(lastIdx);
|
|
144
146
|
if (tail.length) {
|
|
145
|
-
chunks.push({ prefix: tail, token: "", type:
|
|
147
|
+
chunks.push({ prefix: tail, token: "", type: TokenTypes.none });
|
|
146
148
|
}
|
|
147
149
|
return chunks;
|
|
148
150
|
}
|
|
149
151
|
;
|
|
150
152
|
function getTokenType(token) {
|
|
151
153
|
if (token.startsWith("#"))
|
|
152
|
-
return
|
|
153
|
-
return
|
|
154
|
+
return TokenTypes.colour;
|
|
155
|
+
return TokenTypes.number;
|
|
154
156
|
}
|