fix(core): phase-sync useTrail children on loop iterations#2435
Merged
Conversation
🦋 Changeset detectedLatest commit: 8ad79e6 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.
|
Closes #1063. useTrail chains each child via `to: parent.springs`, so every parent change re-enters the child's `_start` → `AnimatedValue.reset()`, zeroing its progress each frame. With `loop: true`, the head snap-resets to `from` each cycle but children only chase fluidly — they trap in a narrowing band near the mid-range and never sweep the full distance. Add an internal `Controller.onLoopReset` subscription that fires synchronously inside `flushUpdate` just before `createLoopUpdate` recurses. `useTrail` subscribes every non-head child directly to the head (the only controller whose `flushUpdate` reaches the loop tail — chaining via immediate parents would cascade only one level) and snaps each child back to `from` in phase. The fluid-chain contract relied on by goo-blobs is preserved.
aea84d3 to
8ad79e6
Compare
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 #1063.
Problem
useTrailchains each child viato: parent.springs, so every parent change re-enters the child's_start→AnimatedValue.reset(), zeroing its progress each frame. Withloop: true, the head snap-resets tofromeach cycle but children only chase fluidly — they trap in a narrowing band near the mid-range and never sweep the full from→to distance. Deeper children trap further from the target.Fix
Add an internal
Controller.onLoopResetsubscription that fires synchronously insideflushUpdatejust beforecreateLoopUpdaterecurses.useTrailsubscribes every non-head child directly to the head controller — the only controller whoseflushUpdatereaches the loop tail (children'stois fluid, so their updates never finish). Chaining via immediate parents would cascade only one level; subscribing to the head keeps the whole trail in phase.On loop restart, each child snaps back to
fromviactrl.start({ reset: true }), then resumes chasing the parent through the fluid chain. The fluid-chain contract (load-bearing for thegoo-blobsmouse-follow demo) is preserved — the existing'has each spring follow the spring before it'test still passes.Out of scope
looptriggers when the head finishes, so deeper springs can still be cut off before reachingtounder short-duration configs. That's a separate API design choice worth a follow-up.