Repository navigation
Fix rotateZ in shared layout animations - #3926
Merged
Merged
Conversation
buildProjectionTransform writes rotate, rotateX and rotateY but not rotateZ, so an element with rotateZ: 45 and a layoutId renders at 0deg while the layout animation runs and is left at transform: none after it. rotate works. The test toggles between two layoutId elements and checks the computed angle mid-animation and after. Fails on React 18 and 19, and on framer-motion 11.0.25. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LgArLzUBuwdUzKQ6xBMEsT
buildProjectionTransform and hasTransform ignored rotateZ, so once an element was projected its rotateZ was dropped from the transform. And mixValues only mixed `rotate` during a shared layout animation, so rotateX, rotateY, rotateZ, skewX and skewY were frozen at their animation-start snapshot (usually 0) until the animation finished. Fixes #2611 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UAj9LiVmna4fj5PehzthiD
skewX now interpolates between the follow and lead values during a shared layout animation, matching rotate. Mirror shared-promote-new-mix-rotate and expect the half-way value. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UAj9LiVmna4fj5PehzthiD
Collaborator
Author
|
Generated by Claude Code |
This was referenced Oct 14, 2026
On a slow CI runner the first frame can arrive late, and both tests assumed it wouldn't: - spring-value "re-attaching mid-animation runs a single animation" expected exactly one change per frame. When the first frame is more than MAX_START_DELAY (40ms) late, resolveStartTime restarts the animation from that frame, so it doesn't move and there are 4 changes in 5 frames. It now checks for at most one change per frame, which still fails with two animations writing. - TransformAnimation "keeps every repeat of a short animation that repeats many times" sampled the WAAPI keyframes as if they started when the animation did. They're built from when they're committed, so a late first frame shifted the sample by the delay. It now offsets by the difference between the two start times. Both failures reproduce locally by blocking the main thread before the first frame, and pass with these changes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UAj9LiVmna4fj5PehzthiD
Found by stalling the main thread before the first frame: - TransformAnimation "composes every transform on an element into one WAAPI animation" expected a duration of at most 1000ms. When the first frame is late enough to restart the animation, the duration is the full 1000ms plus floating point error (1000.0000000000002). - css-variables "should correctly animate previously unencountered variables" expected exactly one update. A restart adds an update at the animation's start, so it now checks the final update. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UAj9LiVmna4fj5PehzthiD
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.
Requested by Matt · project thread
Before: An element with
rotateZand alayoutIdlost its rotation during a shared layout animation. It rendered at 0deg while the animation ran.buildProjectionTransformandhasTransformalso ignoredrotateZ, so a projected element could end up without itsrotateZ.rotateX,rotateY,skewXandskewYhad the same mid-animation problem. Onlyrotateworked.After:
rotateZbehaves the same asrotateduring the animation (it mixes from the old element's value to the new element's live value) and after it.How:
buildProjectionTransformnow writesrotateZ(...)afterrotateY, which matchestransformPropOrder, andhasTransformnow checksrotateZ.mixValuesused to mix onlyrotate.animationValuesis a{ ...latestValues }snapshot taken when the animation starts, so every other distorting transform stayed at that snapshot (usually 0) until the animation finished. It now loops overrotate,rotateX,rotateY,rotateZ,skewXandskewY, which are the valuesresetSkewAndRotationalready treats as non-projectable.Fixes #2611
Tests
tests/react/issue-2611.spec.ts(from the repro branch): failed before the fix on React 18 and React 19, with the angle at 0deg mid-animation. Passes on both after the fix.buildProjectionTransform(rotateZ),hasTransform(all rotate axes) andmixValues(rotate/rotateX/Y/Z and skewX/Y). Each failed before the fix.test-client935 passed,test-server51 passed, motion-dom 834 total (831 passed, 3 skipped).issue-2611|layoutsubset: React 19 39/39 passed. On React 18 everything passed exceptanimate-view.spec.ts, which requires React 19 and fails on React 18 by design.Bundlesize (gzip)
size-rollup-motion.js: +65 B (42640 → 42705)size-rollup-m-dom-max.js: +65 B (42777 → 42842)🤖 Generated with Claude Code
https://claude.ai/code/session_01UAj9LiVmna4fj5PehzthiD
Generated by Claude Code