-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
feat: add Spring and Tween classes #11519
Conversation
🦋 Changeset detectedLatest commit: 6acaddc The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Correct me if I'm wrong, but the value of the spring would get out of sync with the external state if you try to set it directly, no? let { number } = $props();
const thing = new Spring(() => number);
thing.set(5); // oops now we’re out of sync I think a value change callback could be added to avoid such cases. let { number = $bindable() } = $props();
const thing = new Spring(() => number, {
onChange: (v) => (number = v)
});
thing.set(5); // still in sync |
Yes, and the solution is 'don't do that'. In general you wouldn't need to let progress = $state(0);
let spring = new Spring(() => progress);
function increment() {
if (progress === 10) {
spring.set(0, { instant: true });
progress = 0;
} else {
progress += 1;
}
} It would be strange to have an |
Yeah fair. I just worry this might cause weird bugs because you expect the values to always be in sync. |
Possible to sneak in this as well? #9141 (comment) |
Because of the function case, it feels like it might be better to have some kind of Like:
This works even if you're using
I like that this way you no longer have to know what the final spring position is in order to skip the animation. That seems really nice for the function case. I also think a |
I'd love to have |
I'd love to see this in 5.0 and would like to work on it, @Rich-Harris do you have any input on the proposed APIs or should I just work on merge conflicts and the Tween class? |
In this context: My app heavily relies on many sophisticated spring animations and Sveltte 4 custom stores (based on writable, etc.). I actually created kind of an rendering engine based on Svelte's building blocks. I believe that this new spring class adapts all subtleties from the previous API/implementation, e.g. hard and soft props, stiffness and dampening, still I am a bit afraid that I can't replicate all the motions, also there would be some significant refactoring effort, basically rewriting the whole thing. I am still refactoring hundreds of legacy .svelte files to Svelte 5. BTW, Svelte 5 was really drop-in for me and the legacy mode is a excellent way to nudge the community to a migration without breaking changes! 🙂 @Rich-Harris do you already know if you gonna remove (1) the prior spring API and (2) and the Svelte 4 stores, writable etc. in Svelte 6 or in any version after 6? Just to be prepared to plan our future, looking fwd to your reply! |
|
preview: https://svelte-dev-git-preview-svelte-11519-svelte.vercel.app/ this is an automated message |
A few changes:
One problem: Also, this PR deprecates the existing |
I think the function case should expose an const spring = Spring.of(() => value, {
onChange(v) {
value = v;
}
}); This would get called when the spring's value reaches its target to avoid calling the callback repeatedly. EDIT: I just noticed I had already made a similar comment above. I still think it's not a bad idea to have this though. |
What for? You can just track |
This adds a
Spring
class as an alternative to the existingspring
store factory. The behaviour is essentially identical, but the API is slightly different:instant
andpreserveMomentum
are clearer thanhard
andsoft
, which seem like they're somehow symmetrical but are in fact unrelated.To keep a spring in sync with some other value (such as a prop), use
Spring.of
:In this case it's still possible to do
thing.set(value, opts)
, since you might (e.g.) need to use{ instant: true }
, and making it 'readonly' in the function case could be overly restrictive.spring.set
will return a promise that resolves whenspring.current
reachesspring.target
. If you don't need the promise, and don't need to passinstant
orpreserveMomentum
options, you can manipulatespring.target
directly. (This does mean we have two ways of doing things, but we need to exposespring.target
anyway and it would be weird if it was readonly.)A nice thing about using classes: you can do
const spring = new Spring(...)
instead of always having to come up with a more descriptive name.TODO
Tween
class to go withSpring
spring
tests to adapt — not totally sure what tests would look like here)Before submitting the PR, please make sure you do the following
feat:
,fix:
,chore:
, ordocs:
.Tests and linting
pnpm test
and lint the project withpnpm lint