@xtia/timeline 1.1.7 → 1.1.8

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;
@@ -147,6 +147,9 @@ export declare class Timeline {
147
147
  * Performs a smooth-seek through a range at (1000 × this.timeScale) units per second
148
148
  */
149
149
  play(range: TimelineRange, easer?: Easer): Promise<void>;
150
+ private playWithInterval;
151
+ private playWithRAF;
152
+ private next;
150
153
  /**
151
154
  * Stops normal progression instigated by play()
152
155
  *
@@ -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;
@@ -311,9 +313,8 @@ export class Timeline {
311
313
  ? sortTweens
312
314
  : sortReverse);
313
315
  }
314
- play(arg = default_fps, easer) {
315
- if (this.interval !== null)
316
- this.pause();
316
+ play(arg, easer) {
317
+ this._pause?.();
317
318
  if (this.smoothSeeker) {
318
319
  this.smoothSeeker.pause();
319
320
  this.smoothSeeker.seek(this.smoothSeeker.end);
@@ -323,44 +324,72 @@ export class Timeline {
323
324
  this.seek(arg.start);
324
325
  return this.seek(arg.end, arg.duration / this.timeScale, easer);
325
326
  }
327
+ if (arg !== undefined && requestAnimFrame) {
328
+ this.playWithRAF();
329
+ return;
330
+ }
331
+ this.playWithInterval(arg ?? default_fps);
332
+ }
333
+ playWithInterval(fps) {
326
334
  let previousTime = Date.now();
327
- this.interval = setInterval(() => {
335
+ const interval = setInterval(() => {
328
336
  const newTime = Date.now();
329
337
  const elapsed = newTime - previousTime;
330
338
  previousTime = newTime;
331
339
  let delta = elapsed * this.timeScale;
332
- if (this._currentTime + delta <= this._endPosition) {
333
- this.currentTime += delta;
340
+ this.next(delta);
341
+ }, 1000 / fps);
342
+ this._pause = () => clearInterval(interval);
343
+ }
344
+ playWithRAF() {
345
+ let previousTime = null;
346
+ let rafId;
347
+ const frame = (currentTime) => {
348
+ if (previousTime === null) {
349
+ previousTime = currentTime;
350
+ }
351
+ const elapsed = currentTime - previousTime;
352
+ previousTime = currentTime;
353
+ let delta = elapsed * this.timeScale;
354
+ this.next(delta);
355
+ rafId = requestAnimFrame(frame);
356
+ };
357
+ rafId = requestAnimFrame(frame);
358
+ this._pause = () => cancelAnimFrame(rafId);
359
+ }
360
+ next(delta) {
361
+ if (this._currentTime + delta <= this._endPosition) {
362
+ this.currentTime += delta;
363
+ return;
364
+ }
365
+ // overshot; perform restart/pause endAction
366
+ if (this.endAction.type == EndAction.restart) {
367
+ const loopRange = this.endAction.at.to(this._endPosition);
368
+ const loopLen = loopRange.duration;
369
+ if (loopLen <= 0) {
370
+ const target = Math.min(this._currentTime + delta, this._endPosition);
371
+ this.seek(target);
334
372
  return;
335
373
  }
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);
374
+ while (delta > 0) {
375
+ const distanceToEnd = this._endPosition - this._currentTime;
376
+ if (delta < distanceToEnd) {
377
+ this.seek(this._currentTime + delta);
343
378
  return;
344
379
  }
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
380
  this.seek(this._endPosition);
359
- this.pause();
360
- return;
381
+ delta -= distanceToEnd;
382
+ this.seek(this.endAction.at);
361
383
  }
362
- this.currentTime += delta;
363
- }, 1000 / arg);
384
+ return;
385
+ }
386
+ if (this.endAction.type == EndAction.pause) {
387
+ this.seek(this._endPosition);
388
+ this.pause();
389
+ return;
390
+ }
391
+ // endaction must be "continue" or "wrap"
392
+ this.currentTime += delta;
364
393
  }
365
394
  /**
366
395
  * Stops normal progression instigated by play()
@@ -369,10 +398,10 @@ export class Timeline {
369
398
  *
370
399
  */
371
400
  pause() {
372
- if (this.interval === null)
401
+ if (this._pause === null)
373
402
  return;
374
- clearInterval(this.interval);
375
- this.interval = null;
403
+ this._pause();
404
+ this._pause = null;
376
405
  }
377
406
  step(delta = 1) {
378
407
  this.currentTime += delta * this.timeScale;
@@ -398,12 +427,25 @@ export class Timeline {
398
427
  */
399
428
  at(position, action, reverse) {
400
429
  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);
430
+ if (!action) {
431
+ if (reverse) {
432
+ if (reverse === true)
433
+ throw new Error("Invalid call");
434
+ point.reverseOnly.apply(reverse);
435
+ }
436
+ return this.createChainingInterface(point.position);
437
+ }
438
+ if (reverse) {
439
+ if (reverse === true) {
440
+ point.apply(action);
441
+ }
442
+ else {
443
+ point.applyDirectional(action, reverse);
444
+ }
445
+ }
446
+ else {
447
+ point.forwardOnly.apply(action);
448
+ }
407
449
  return this.createChainingInterface(point.position);
408
450
  }
409
451
  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.8",
4
4
  "repository": {
5
5
  "url": "https://github.com/tiadrop/timeline",
6
6
  "type": "github"