Skip to content

Fix rotateZ in shared layout animations - #3926

Merged
mattgperry merged 5 commits into
mainfrom
claude/project-thread-q79mtn-issue-2611
Oct 16, 2026
Merged

mattgperry merged 5 commits into
mainfrom
claude/project-thread-q79mtn-issue-2611

Conversation

@mattgperry

Copy link
Copy Markdown
Collaborator

Requested by Matt · project thread

Before: An element with rotateZ and a layoutId lost its rotation during a shared layout animation. It rendered at 0deg while the animation ran. buildProjectionTransform and hasTransform also ignored rotateZ, so a projected element could end up without its rotateZ. rotateX, rotateY, skewX and skewY had the same mid-animation problem. Only rotate worked.

After: rotateZ behaves the same as rotate during the animation (it mixes from the old element's value to the new element's live value) and after it.

How: buildProjectionTransform now writes rotateZ(...) after rotateY, which matches transformPropOrder, and hasTransform now checks rotateZ. mixValues used to mix only rotate. animationValues is 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 over rotate, rotateX, rotateY, rotateZ, skewX and skewY, which are the values resetSkewAndRotation already treats as non-projectable.

Fixes #2611

Tests

  • Playwright 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.
  • New Jest tests for buildProjectionTransform (rotateZ), hasTransform (all rotate axes) and mixValues (rotate/rotateX/Y/Z and skewX/Y). Each failed before the fix.
  • Unit suites: framer-motion test-client 935 passed, test-server 51 passed, motion-dom 834 total (831 passed, 3 skipped).
  • Playwright issue-2611|layout subset: React 19 39/39 passed. On React 18 everything passed except animate-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)
  • All other bundles: 0 B. All bundles are within their limits.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UAj9LiVmna4fj5PehzthiD


Generated by Claude Code

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

mattgperry commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator Author

Looks like a flake, please re-run [Edit: wrong.] The failure was real: TransformAnimation.test.ts "keeps every repeat of a short animation that repeats many times" sampled WAAPI keyframes as if they were built when the animation started. When the first frame was late, the result shifted by that delay. That test is now fixed in #3929, and the same commit is cherry-picked here as 9641813.


Generated by Claude Code

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
@mattgperry
mattgperry merged commit c5fe1e6 into main Oct 16, 2026
6 checks passed
@mattgperry
mattgperry deleted the claude/project-thread-q79mtn-issue-2611 branch October 16, 2026 08:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] RotateZ not working in Shared Layout Animation

2 participants