New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Erratic spring behaviour #7010
Comments
Is there any workaround to this? |
The bug is caused by too long deltaTime between frames of the requestAnimationFrame, the calculation of the spring physics is not intended for such a long “prediction”. Since requestAnimationFrame does not call a callback while the tab with the page is invisible deltaTime becomes too big. Limiting deltaTime to a maximum of 1/24 of a second will help here, or taking into account the time that the tab was closed using the visibility API, for example 109 line of svelte/packages/src/motion/spring.js dt: ((now - last_time) * 60) / 1000 could be changed to dt: ((Math.min(now - last_time, 1000 / 24)) * 60) / 1000 |
Recently, I wrote a similar function to svelte/spring. I needed a faster version of the spring, so I removed support for objects and arrays, and also fixed a bug with excessively large delta time. import { writable } from 'svelte/store';
export function spring(value: number, { stiffness = 0.15, damping = 0.8, precision = 0.01 }) {
const store = writable(value);
let lastTime: number;
let lastValue = value;
let currentValue = value;
let targetValue = value;
let running = false;
function set(newValue: number) {
targetValue = newValue;
if (!running) {
running = true;
lastTime = performance.now();
requestAnimationFrame(loop);
}
}
function loop() {
const currentTime = performance.now();
const deltaTime = Math.min(currentTime - lastTime, 42) * 0.06;
const delta = targetValue - currentValue;
const velocity = (currentValue - lastValue) / deltaTime;
const spring = stiffness * delta;
const damper = damping * velocity;
const acceleration = spring - damper;
const d = (velocity + acceleration) * deltaTime;
lastValue = currentValue;
if (Math.abs(d) < precision && Math.abs(delta) < precision) {
store.set((currentValue = targetValue));
running = false;
} else {
store.set((currentValue = currentValue + d));
lastTime = currentTime;
requestAnimationFrame(loop);
}
}
return {
set,
subscribe: store.subscribe
};
} Currently, the function does not support SSR. To add support, you need to replace requestAnimationFrame with raf as follows: const is_client = typeof window !== 'undefined';
function noop() {}
const raf = is_client ? (cb) => requestAnimationFrame(cb) : noop; |
Describe the bug
If the time between frames is high (i.e. more than a small fraction of a second), it can cause springs to rapidly jump to unexpectedly large or small values. This creates a poor user experience, and can cause error conditions (such as when it wasn't anticipated that an overdamped spring could overshoot the target.)
The time between frames can increase for three main reasons:
I've experienced all 3 cases, and this effects all uses of springs to varying extents.
(I will submit a pull request separately that is intended to fix this)
Reproduction
https://svelte.dev/repl/8a52664675434394899f3e58d8f542a3?version=3.44.2
Logs
No response
System Info
Severity
annoyance
The text was updated successfully, but these errors were encountered: