sveltejs / svelte

Cybernetically enhanced web apps
https://svelte.dev
MIT License
78.17k stars 4.08k forks source link

`spring` performs differently on displays with different refresh rates #10717

Open GoodBoyNinja opened 6 months ago

GoodBoyNinja commented 6 months ago

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

rmunn commented 6 months ago

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 commented 6 months ago

There's currently a large PR outstanding to refactor rendering and transitions: https://github.com/sveltejs/svelte/pull/10594. It's very likely that a bugfix for the spring code will have to wait untli that PR is finished.

GoodBoyNinja commented 6 months ago

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

https://github.com/sveltejs/svelte/blob/3fd02f1c49fd43f59b27dbd4822f351693a63301/packages/svelte/src/motion/spring.js#L19

https://github.com/sveltejs/svelte/blob/3fd02f1c49fd43f59b27dbd4822f351693a63301/packages/svelte/src/motion/spring.js#L109