diff --git a/.changeset/modern-wasps-punch.md b/.changeset/modern-wasps-punch.md new file mode 100644 index 000000000000..1fa0b413007a --- /dev/null +++ b/.changeset/modern-wasps-punch.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: prevent long delays causing erratic spring behaviour diff --git a/packages/svelte/src/motion/spring.js b/packages/svelte/src/motion/spring.js index bc30ce957854..1c8e5f15c048 100644 --- a/packages/svelte/src/motion/spring.js +++ b/packages/svelte/src/motion/spring.js @@ -108,12 +108,17 @@ export function spring(value, opts = {}) { return false; } inv_mass = Math.min(inv_mass + inv_mass_recovery_rate, 1); + + // clamp elapsed time to 1/30th of a second, so that longer pauses + // (blocked thread or inactive tab) don't cause the spring to go haywire + const elapsed = Math.min(now - last_time, 1000 / 30); + /** @type {TickContext} */ const ctx = { inv_mass, opts: spring, settled: true, - dt: ((now - last_time) * 60) / 1000 + dt: (elapsed * 60) / 1000 }; // @ts-ignore const next_value = tick_spring(ctx, last_value, value, target_value); @@ -236,6 +241,10 @@ export class Spring { this.#task ??= loop((now) => { this.#inverse_mass = Math.min(this.#inverse_mass + inv_mass_recovery_rate, 1); + // clamp elapsed time to 1/30th of a second, so that longer pauses + // (blocked thread or inactive tab) don't cause the spring to go haywire + const elapsed = Math.min(now - this.#last_time, 1000 / 30); + /** @type {import('./private').TickContext} */ const ctx = { inv_mass: this.#inverse_mass, @@ -245,7 +254,7 @@ export class Spring { precision: this.#precision.v }, settled: true, - dt: ((now - this.#last_time) * 60) / 1000 + dt: (elapsed * 60) / 1000 }; var next = tick_spring(ctx, this.#last_value, this.#current.v, this.#target.v);