fix(core): run async script to to completion under skipAnimation#2438
Merged
Conversation
When `Globals.skipAnimation` was true, an async script `to` (e.g. `enter:
() => async next => await next({ opacity: 1 })`) bailed before applying any
values, so the spring never reached its declared end state. Apply each step
with `immediate: true` and let the script run to completion so the spring
lands on the final `next(...)` value just like it would with animations on.
A 1024-call safety cap protects against unterminating scripts that would
otherwise become a tight microtask loop without animation frames to pace them.
Closes #1429
🦋 Changeset detectedLatest commit: dd3933e The changes in this PR will be included in the next version bump. This PR includes changesets to release 12 packages
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 |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This was referenced May 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1429.
When
Globals.skipAnimationis true, async functiontos (e.g.enter: () => async next => await next({ opacity: 1 })) should land on the same end state as if animations had run normally — that's how the reduced-motion contract is documented in useReducedMotion. Today they don't:runAsyncshort-circuits before the script ever callstarget.start, so values declared inside the script are silently dropped. Users have had to detectskipAnimationthemselves and rewrite their async chains as flatimmediate: trueobjects (workaround in the thread).Behaviour now
runAsync.animate()applies eachnext(...)call withimmediate: trueand lets the script run through to completion. End states underskipAnimation:toshape{ x: 2 }x === 2(unchanged)[{ x: 1 }, { x: 2 }]x === 2async next => { await next({ x: 1 }); await next({ x: 2 }) }x === 2async next => { while (true) await next({ x: n++ }) }nextcallsThe 1024-call cap
Without animation frames to pace it,
while (true) await next(...)becomes a tight microtask loop that would hang the host. The cap is generous enough to be invisible for real chains and acts as a hard stop for runaway scripts. If you want repeating animations, use theloopprop — it does the right thing underskipAnimation.Behavioural change to be aware of
Previously, setting
skipAnimationmid-flight would abort the async script on its nextawait next(...). It now lets the script finish naturally — each remaining step jumps to its target instead of being skipped over. In-flightSpringValueanimations are unchanged: they still ride out their remaining frames (the global flag is only checked when a new animation starts).