From 6968969c9dbe03722c1cfb7ccfa0c93b8b04e3b8 Mon Sep 17 00:00:00 2001 From: Willy Brauner Date: Sat, 10 Oct 2026 14:24:46 +0200 Subject: [PATCH] fix: fix stop autoplay --- .changeset/fix-stop-autoplay.md | 28 ++++++++++++++++++++++++++++ README.md | 2 ++ src/core/Timeline.ts | 3 +++ tests/Timeline.stop.test.ts | 24 ++++++++++++++++++++++++ 4 files changed, 57 insertions(+) create mode 100644 .changeset/fix-stop-autoplay.md diff --git a/.changeset/fix-stop-autoplay.md b/.changeset/fix-stop-autoplay.md new file mode 100644 index 00000000..c70623d3 --- /dev/null +++ b/.changeset/fix-stop-autoplay.md @@ -0,0 +1,28 @@ +--- +"@wbe/interpol": patch +--- + +Fix Timeline autoplay not being canceled by an explicit call made synchronously after `add()`. An explicit `stop()`, `play()` or `reverse()` now cancels the pending autoplay. + +Before: + +```ts +const tl = new Timeline() +tl.add({ /* ... */ }) +// ignored: the timeline was played anyway by the autoplay, +// and stayed registered in the ticker +tl.stop() +``` + +After: + +```ts +const tl = new Timeline() +tl.add({ /* ... */ }) +// cancels the pending autoplay: the timeline stays stopped +tl.stop() +``` + +Same for `reverse()`, which was switched back to forward by the autoplay and now stays reversed. + +To dispose an instance, `stop()` it and drop your reference: nothing else retains it. diff --git a/README.md b/README.md index 616d8a5c..6bdc839e 100644 --- a/README.md +++ b/README.md @@ -376,6 +376,7 @@ itp.pause() itp.resume() // Stop the interpol, will reset time, delta and progress. +// To dispose an instance, stop it and drop your reference, there is no kill() to call. // stop(): void itp.stop() @@ -446,6 +447,7 @@ tl.pause() tl.resume() // stop the timeline, will reset time, delta and progress. +// To dispose an instance, stop it and drop your reference, there is no kill() to call. // stop(): void tl.stop() diff --git a/src/core/Timeline.ts b/src/core/Timeline.ts index 35ceeb2c..780b5a79 100644 --- a/src/core/Timeline.ts +++ b/src/core/Timeline.ts @@ -157,6 +157,8 @@ export class Timeline { if (!this.isPaused && !this.inTl && !this.#autoplayScheduled) { this.#autoplayScheduled = true queueMicrotask(() => { + // Canceled by an explicit play(), reverse() or stop() call in the meantime + if (!this.#autoplayScheduled) return this.#autoplayScheduled = false this.play() }) @@ -236,6 +238,7 @@ export class Timeline { this.#isPlaying = false this.#isPaused = false this.#isReversed = false + this.#autoplayScheduled = false this.#onAllAdds((e) => { e.progress.current = from e.progress.last = from diff --git a/tests/Timeline.stop.test.ts b/tests/Timeline.stop.test.ts index 41651268..8f370136 100644 --- a/tests/Timeline.stop.test.ts +++ b/tests/Timeline.stop.test.ts @@ -58,4 +58,28 @@ describe.concurrent("Timeline stop", () => { return Promise.all(tls) }) + + it("Timeline should not autoplay if stop() is called synchronously after add()", async () => { + const tlOnUpdate = vi.fn() + const itpOnUpdate = vi.fn() + const tl = new Timeline({ onUpdate: tlOnUpdate }) + tl.add({ v: [0, 100], duration: 50, onUpdate: itpOnUpdate }) + tl.stop() + + await wait(100) + expect(tlOnUpdate).toHaveBeenCalledTimes(0) + expect(itpOnUpdate).toHaveBeenCalledTimes(0) + expect(tl.isPlaying).toBe(false) + }) + + it("Timeline should stay reversed if reverse() is called synchronously after add()", async () => { + const tl = new Timeline() + tl.add({ v: [0, 100], duration: 200 }) + tl.reverse() + + // let the autoplay microtask run + await Promise.resolve() + expect(tl.isReversed).toBe(true) + tl.stop() + }) })