@xtia/timeline 1.1.7 → 1.1.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 CHANGED
@@ -515,6 +515,14 @@ Listeners will be invoked with a [`PointEvent`](#pointevent-interface) when a se
515
515
 
516
516
  This point's position on the Timeline.
517
517
 
518
+ ##### `forwardOnly: Emitter<PointEvent>`
519
+
520
+ Provides an emitter that forwards emissions triggered by forward-moving seeks.
521
+
522
+ ##### `reverseOnly: Emitter<PointEvent>`
523
+
524
+ Provides an emitter that forwards emissions triggered by backward-moving seeks.
525
+
518
526
  #### Methods
519
527
 
520
528
  ##### `range(duration): TimelineRange`
@@ -543,14 +551,6 @@ Creates a `Promise` that will be resolved when the Timeline first seeks to/past
543
551
 
544
552
  The resolved value indicates the direction of the seek that triggered resolution.
545
553
 
546
- ##### `forwardOnly(): Emitter<PointEvent>`
547
-
548
- Creates an emitter that forwards emissions triggered by forward-moving seeks.
549
-
550
- ##### `reverseOnly(): Emitter<PointEvent>`
551
-
552
- Creates an emitter that forwards emissions triggered by backward-moving seeks.
553
-
554
554
  ##### `applyDirectional(apply, revert): UnsubscribeFunc`
555
555
 
556
556
  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.
@@ -46,16 +46,16 @@ export declare class TimelinePoint extends Emitter<PointEvent> {
46
46
  */
47
47
  seek(duration: number, easer?: Easer): Promise<void>;
48
48
  /**
49
- * Creates an emitter that only emits on forward-moving seeks
49
+ * An point emitter that only emits on forward-moving seeks
50
50
  * @returns Listenable: emits forward-seeking point events
51
51
  */
52
- forwardOnly(): Emitter<PointEvent>;
52
+ get forwardOnly(): Emitter<PointEvent>;
53
53
  private _forwardOnly?;
54
54
  /**
55
- * Creates an emitter that only emits on backward-moving seeks
55
+ * An point emitter that only emits on backward-moving seeks
56
56
  * @returns Listenable: emits backward-seeking point events
57
57
  */
58
- reverseOnly(): Emitter<PointEvent>;
58
+ get reverseOnly(): Emitter<PointEvent>;
59
59
  private _reverseOnly?;
60
60
  filter(check: (event: PointEvent) => boolean): Emitter<PointEvent>;
61
61
  /**
package/internal/point.js CHANGED
@@ -41,19 +41,19 @@ export class TimelinePoint extends Emitter {
41
41
  return this.timeline.seek(this.position, duration, easer);
42
42
  }
43
43
  /**
44
- * Creates an emitter that only emits on forward-moving seeks
44
+ * An point emitter that only emits on forward-moving seeks
45
45
  * @returns Listenable: emits forward-seeking point events
46
46
  */
47
- forwardOnly() {
47
+ get forwardOnly() {
48
48
  if (!this._forwardOnly)
49
49
  this._forwardOnly = this.filter(1);
50
50
  return this._forwardOnly;
51
51
  }
52
52
  /**
53
- * Creates an emitter that only emits on backward-moving seeks
53
+ * An point emitter that only emits on backward-moving seeks
54
54
  * @returns Listenable: emits backward-seeking point events
55
55
  */
56
- reverseOnly() {
56
+ get reverseOnly() {
57
57
  if (!this._reverseOnly)
58
58
  this._reverseOnly = this.filter(-1);
59
59
  return this._reverseOnly;
@@ -42,7 +42,7 @@ export declare class Timeline {
42
42
  get end(): TimelinePoint;
43
43
  private _currentTime;
44
44
  private _endPosition;
45
- private interval;
45
+ private _pause;
46
46
  private points;
47
47
  private endAction;
48
48
  private ranges;
@@ -118,7 +118,8 @@ export declare class Timeline {
118
118
  */
119
119
  range(): TimelineRange;
120
120
  /**
121
- * Seeks the Timeline to a specified position, triggering in order any point and range subscriptions between its current and new positions
121
+ * Seeks the Timeline to a specified position, triggering in order any point and range
122
+ * subscriptions between its current and new positions
122
123
  * @param toPosition
123
124
  */
124
125
  seek(toPosition: number | TimelinePoint): void;
@@ -133,6 +134,14 @@ export declare class Timeline {
133
134
  */
134
135
  seek(toPosition: number | TimelinePoint, durationMs: number, easer?: Easer | keyof typeof easers): Promise<void>;
135
136
  seek(toPosition: number | TimelinePoint, duration: Period, easer?: Easer | keyof typeof easers): Promise<void>;
137
+ /**
138
+ * Smooth-seeks through a range over a given duration
139
+ * @param range The range to seek through
140
+ * @param durationMs Smooth-seek duration
141
+ * @param easer Optional easing function
142
+ */
143
+ seek(range: TimelineRange, durationMs: number, easer?: Easer | keyof typeof easers): Promise<void>;
144
+ seek(range: TimelineRange, duration: Period, easer?: Easer | keyof typeof easers): Promise<void>;
136
145
  private seekDirect;
137
146
  private seekWrapped;
138
147
  private seekPoints;
@@ -147,6 +156,9 @@ export declare class Timeline {
147
156
  * Performs a smooth-seek through a range at (1000 × this.timeScale) units per second
148
157
  */
149
158
  play(range: TimelineRange, easer?: Easer): Promise<void>;
159
+ private playWithInterval;
160
+ private playWithRAF;
161
+ private next;
150
162
  /**
151
163
  * Stops normal progression instigated by play()
152
164
  *
@@ -3,6 +3,8 @@ import { TimelinePoint } from "./point";
3
3
  import { TimelineRange } from "./range";
4
4
  import { clamp } from "./utils";
5
5
  const default_fps = 60;
6
+ const requestAnimFrame = globalThis?.requestAnimationFrame;
7
+ const cancelAnimFrame = globalThis?.cancelAnimationFrame;
6
8
  const EndAction = {
7
9
  pause: 0,
8
10
  continue: 1,
@@ -27,7 +29,7 @@ export class Timeline {
27
29
  * Returns true if this Timeline is currently progressing via `play()`, otherwise false
28
30
  */
29
31
  get isPlaying() {
30
- return this.interval !== null;
32
+ return !!this._pause;
31
33
  }
32
34
  /**
33
35
  * Returns a fixed point at the current end of the Timeline
@@ -71,7 +73,7 @@ export class Timeline {
71
73
  this.timeScale = 1;
72
74
  this._currentTime = 0;
73
75
  this._endPosition = 0;
74
- this.interval = null;
76
+ this._pause = null;
75
77
  this.points = [];
76
78
  this.ranges = [];
77
79
  this.currentSortDirection = 0;
@@ -174,6 +176,10 @@ export class Timeline {
174
176
  return range;
175
177
  }
176
178
  seek(to, duration, easer) {
179
+ if (to instanceof TimelineRange) {
180
+ this.seek(to.start);
181
+ return this.seek(to, duration, easer);
182
+ }
177
183
  const durationMs = typeof duration == "object"
178
184
  ? duration.asMilliseconds
179
185
  : duration;
@@ -193,7 +199,6 @@ export class Timeline {
193
199
  this.seekDirect(interruptPosition);
194
200
  }
195
201
  if (!durationMs) {
196
- const fromTime = this._currentTime;
197
202
  this.seekDirect(toPosition);
198
203
  this._frameEvents?.emit();
199
204
  // only add Promise overhead if duration is explicitly 0
@@ -311,9 +316,8 @@ export class Timeline {
311
316
  ? sortTweens
312
317
  : sortReverse);
313
318
  }
314
- play(arg = default_fps, easer) {
315
- if (this.interval !== null)
316
- this.pause();
319
+ play(arg, easer) {
320
+ this._pause?.();
317
321
  if (this.smoothSeeker) {
318
322
  this.smoothSeeker.pause();
319
323
  this.smoothSeeker.seek(this.smoothSeeker.end);
@@ -323,44 +327,73 @@ export class Timeline {
323
327
  this.seek(arg.start);
324
328
  return this.seek(arg.end, arg.duration / this.timeScale, easer);
325
329
  }
330
+ if (arg !== undefined && requestAnimFrame) {
331
+ this.playWithRAF();
332
+ return;
333
+ }
334
+ this.playWithInterval(arg ?? default_fps);
335
+ }
336
+ playWithInterval(fps) {
326
337
  let previousTime = Date.now();
327
- this.interval = setInterval(() => {
338
+ const interval = setInterval(() => {
328
339
  const newTime = Date.now();
329
340
  const elapsed = newTime - previousTime;
330
341
  previousTime = newTime;
331
342
  let delta = elapsed * this.timeScale;
332
- if (this._currentTime + delta <= this._endPosition) {
333
- this.currentTime += delta;
343
+ this.next(delta);
344
+ }, 1000 / fps);
345
+ this._pause = () => clearInterval(interval);
346
+ }
347
+ playWithRAF() {
348
+ let previousTime = null;
349
+ let rafId;
350
+ const frame = (currentTime) => {
351
+ if (previousTime === null) {
352
+ previousTime = currentTime;
353
+ }
354
+ const elapsed = currentTime - previousTime;
355
+ previousTime = currentTime;
356
+ let delta = elapsed * this.timeScale;
357
+ this.next(delta);
358
+ if (this._pause)
359
+ rafId = requestAnimFrame(frame);
360
+ };
361
+ rafId = requestAnimFrame(frame);
362
+ this._pause = () => cancelAnimFrame(rafId);
363
+ }
364
+ next(delta) {
365
+ if (this._currentTime + delta <= this._endPosition) {
366
+ this.currentTime += delta;
367
+ return;
368
+ }
369
+ // overshot; perform restart/pause endAction
370
+ if (this.endAction.type == EndAction.restart) {
371
+ const loopRange = this.endAction.at.to(this._endPosition);
372
+ const loopLen = loopRange.duration;
373
+ if (loopLen <= 0) {
374
+ const target = Math.min(this._currentTime + delta, this._endPosition);
375
+ this.seek(target);
334
376
  return;
335
377
  }
336
- // overshot; perform restart/pause endAction
337
- if (this.endAction.type == EndAction.restart) {
338
- const loopRange = this.endAction.at.to(this._endPosition);
339
- const loopLen = loopRange.duration;
340
- if (loopLen <= 0) {
341
- const target = Math.min(this._currentTime + delta, this._endPosition);
342
- this.seek(target);
378
+ while (delta > 0) {
379
+ const distanceToEnd = this._endPosition - this._currentTime;
380
+ if (delta < distanceToEnd) {
381
+ this.seek(this._currentTime + delta);
343
382
  return;
344
383
  }
345
- while (delta > 0) {
346
- const distanceToEnd = this._endPosition - this._currentTime;
347
- if (delta < distanceToEnd) {
348
- this.seek(this._currentTime + delta);
349
- return;
350
- }
351
- this.seek(this._endPosition);
352
- delta -= distanceToEnd;
353
- this.seek(this.endAction.at);
354
- }
355
- return;
356
- }
357
- if (this.endAction.type == EndAction.pause) {
358
384
  this.seek(this._endPosition);
359
- this.pause();
360
- return;
385
+ delta -= distanceToEnd;
386
+ this.seek(this.endAction.at);
361
387
  }
362
- this.currentTime += delta;
363
- }, 1000 / arg);
388
+ return;
389
+ }
390
+ if (this.endAction.type == EndAction.pause) {
391
+ this.seek(this._endPosition);
392
+ this.pause();
393
+ return;
394
+ }
395
+ // endaction must be "continue" or "wrap"
396
+ this.currentTime += delta;
364
397
  }
365
398
  /**
366
399
  * Stops normal progression instigated by play()
@@ -369,10 +402,10 @@ export class Timeline {
369
402
  *
370
403
  */
371
404
  pause() {
372
- if (this.interval === null)
405
+ if (this._pause === null)
373
406
  return;
374
- clearInterval(this.interval);
375
- this.interval = null;
407
+ this._pause();
408
+ this._pause = null;
376
409
  }
377
410
  step(delta = 1) {
378
411
  this.currentTime += delta * this.timeScale;
@@ -398,12 +431,25 @@ export class Timeline {
398
431
  */
399
432
  at(position, action, reverse) {
400
433
  const point = typeof position == "number" ? this.point(position) : position;
401
- if (reverse === true)
402
- reverse = action;
403
- if (action)
404
- point.apply(reverse
405
- ? (event => event.direction < 0 ? reverse() : action())
406
- : action);
434
+ if (!action) {
435
+ if (reverse) {
436
+ if (reverse === true)
437
+ throw new Error("Invalid call");
438
+ point.reverseOnly.apply(reverse);
439
+ }
440
+ return this.createChainingInterface(point.position);
441
+ }
442
+ if (reverse) {
443
+ if (reverse === true) {
444
+ point.apply(action);
445
+ }
446
+ else {
447
+ point.applyDirectional(action, reverse);
448
+ }
449
+ }
450
+ else {
451
+ point.forwardOnly.apply(action);
452
+ }
407
453
  return this.createChainingInterface(point.position);
408
454
  }
409
455
  createChainingInterface(position) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xtia/timeline",
3
- "version": "1.1.7",
3
+ "version": "1.1.9",
4
4
  "repository": {
5
5
  "url": "https://github.com/tiadrop/timeline",
6
6
  "type": "github"