Skip to content

spring performs differently on displays with different refresh rates #10717

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

Open
GoodBoyNinja opened this issue Mar 6, 2024 · 5 comments
Open

Comments

@GoodBoyNinja
Copy link

Describe the bug

After creating an element with spring stores used for coordinates, testing the animation on different displays results in a noticeably different animation.

My animation on an m1 pro MacBook pro 120hz display:
https://github.com/sveltejs/svelte/assets/66829812/6eb6c1ba-8beb-46da-9fcb-81a950e02e54

The same animation on a 60hz external display:
https://github.com/sveltejs/svelte/assets/66829812/3f98b357-36b5-4aab-a6fe-04600c09d028

The same animation on the 60hz display resolves much slower.

Reproduction

I found this REPL online, where the same behavior can be observed when testing on both a 60hz and a 160hz display
https://svelte.dev/repl/spring?version=4.2.12

Logs

No response

System Info

System:
    OS: macOS 14.3.1
    CPU: (8) arm64 Apple M1 Pro
    Memory: 142.45 MB / 16.00 GB
    Shell: 3.2.57 - /bin/bash
  Binaries:
    Node: 18.13.0 - /usr/local/bin/node
    npm: 9.6.0 - /usr/local/bin/npm
  Browsers:
    Chrome: 122.0.6261.112
    Safari: 17.3.1

Severity

annoyance

@GoodBoyNinja GoodBoyNinja changed the title "spring" performs differently on displays with different refresh rates spring performs differently on displays with different refresh rates Mar 6, 2024
@rmunn
Copy link
Contributor

rmunn commented Mar 7, 2024

Probably has a lot to do with requestAnimationFrame. MDN says "The frequency of calls to the callback function will generally match the display refresh rate." So on the 120Hz display, the spring's tick_spring method is called twice as often and therefore ends up reaching the target value sooner. I haven't dived deep into the transitions code, but I do see a lot of hardcoded 60's in src/motion/spring.js, so there might be an assumption of a 60Hz refresh rate baked into the spring code that needs to change.

@rmunn
Copy link
Contributor

rmunn commented Mar 7, 2024

There's currently a large PR outstanding to refactor rendering and transitions: #10594. It's very likely that a bugfix for the spring code will have to wait untli that PR is finished.

@GoodBoyNinja
Copy link
Author

Probably has a lot to do with requestAnimationFrame. MDN says "The frequency of calls to the callback function will generally match the display refresh rate." So on the 120Hz display, the spring's tick_spring method is called twice as often and therefore ends up reaching the target value sooner. I haven't dived deep into the transitions code, but I do see a lot of hardcoded 60's in src/motion/spring.js, so there might be an assumption of a 60Hz refresh rate baked into the spring code that needs to change.

Thanks for taking a look.
I don't have a lot to add , just mentioning some related lines if someone else wants to take a look at it in the future

const velocity = (current_value - last_value) / (ctx.dt || 1 / 60); // guard div by 0

dt: ((now - last_time) * 60) / 1000

@Arecsu
Copy link

Arecsu commented Feb 14, 2025

This is interesting indeed. With the linear() type of easing already available in CSS, this could be used in so many ways with the current Svelte transition system.

There's the hardcoded 60hz (t) argument to process easings in any given transition. And there's unfortunately no way of overriding that and make usage of linear({values} directly with custom Svelte Transitions. Of course, we can always use WAAPI or other JS animation libraries directly, but we lose the beautiful convenience of having Svelte conditionals like {# if} triggering transitions without further workarounds.

Just today I've spent a huge amount of time trying to find clever ways to manage a wide range of animations in a project. From basic to advanced sequences. Came up with this interesting bug by the way:

Svelte Transition won't trigger the function callback in faster-than-duration toggles for in-out transitions

Back to the point of native CSS linear() easing: this could offload the calculation of interpolated values to the browser and also freedom from (possibly?) hardcoded keyframes all together and increased precision in high refresh rate displays. As long as the linear() is filled with a good amount of values, it works wonders even in complex spring type of easings at high refresh rates. See here!

If I recall correctly, there has been an attempt in reworking the transition system. It would be neat to come up with clever ways to build a new Transition system that could simply handle WAAPI + javascript tick sync if needed + callbacks to handle, for instance, to await for any custom function to finish before unmounting the {# if} block - by default, tied to the finish() callback from a WAAPI, but could potentially be used by external libraries in any other way and so much more. There's even ViewTransition API which looks cool for some element transitions, particularly those from the FLIP technique. See here and here.

By the way, love Svelte so much 💛

@GoodBoyNinja
Copy link
Author

GoodBoyNinja commented Feb 14, 2025

This might sound like a small annoyance but it unfortunately makes transitions animations in svelte completely unreliable. Has it ever been fixed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants