Skip to content

Fix draggable direction when parent is rotated#3589

Open
mattgperry wants to merge 2 commits intomainfrom
worktree-fix-issue-3320
Open

Fix draggable direction when parent is rotated#3589
mattgperry wants to merge 2 commits intomainfrom
worktree-fix-issue-3320

Conversation

@mattgperry
Copy link
Collaborator

Summary

  • When a draggable element is inside a rotated parent motion component (e.g., <motion.div style={{ rotate: 180 }}>), the drag direction was inverted — the element moved opposite to the cursor
  • The root cause: page-space pointer offsets were applied directly as CSS translateX/translateY, which operate in the parent's local coordinate system. A 180° rotation inverts both axes, so positive page-space offsets produce negative visual movement
  • The fix computes the inverse of the accumulated parent transform matrix (rotation + scale) from the visual element tree at drag start, then transforms offsets into the parent's local coordinate system before applying them. Momentum velocity is also corrected

Test plan

  • Added Cypress E2E test (drag-rotated-parent.ts) that verifies element follows cursor when parent has rotate: 180
  • Verified test fails against unmodified code (element moves opposite to cursor)
  • Verified test passes with fix (element follows cursor correctly)
  • Existing drag tests pass on React 18 (24/24)
  • Existing drag tests pass on React 19 (24/24)
  • New test passes on both React 18 and React 19
  • yarn build passes
  • yarn test passes (91 suites, 760 tests)

Fixes #3320

🤖 Generated with Claude Code

When a draggable element is inside a rotated parent motion component,
the drag direction was incorrect because page-space pointer offsets
were applied directly as CSS translateX/translateY, which operate in
the parent's local coordinate system.

The fix computes the inverse of the accumulated parent transform matrix
(rotation + scale) from the visual element tree at drag start, then
transforms the page-space offset into the parent's local coordinate
system before applying it. This ensures the element follows the cursor
correctly regardless of ancestor rotation or scale.

Fixes #3320

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@greptile-apps
Copy link

greptile-apps bot commented Mar 3, 2026

Greptile Summary

This PR fixes a bug where draggable elements moved opposite to the cursor when inside a rotated parent. The fix computes the inverse of the accumulated parent transform matrix (rotation + scale) and applies it to pointer offsets, correctly translating page-space coordinates into the parent's local coordinate system.

Key Changes:

  • Introduced inverseParentTransform matrix cached at drag start in VisualElementDragControls
  • Added calcInverseParentMatrix() function that walks up the visual element tree, accumulates 2D transforms, and returns the inverse
  • Modified onMove handler to transform offsets via applyInverseTransform() before applying as translateX/Y
  • Corrected momentum velocity using the same inverse transform
  • Added comprehensive E2E test verifying 180° rotation case

Implementation Quality:

  • Mathematics is correct (proper 2×2 matrix inversion with determinant check for singularity)
  • Handles edge cases (returns null for zero determinant or no transforms)
  • Performance-conscious (matrix computed once at drag start, efficient arithmetic in hot path)
  • Clean code with clear comments explaining the coordinate system transformation

The fix is well-scoped to 2D transforms (rotate/rotateZ and scale), which covers the common use cases. More complex scenarios like 3D transforms or skew are not addressed but are acceptable limitations for this targeted bugfix.

Confidence Score: 5/5

  • This PR is safe to merge with high confidence
  • The implementation is mathematically sound, well-tested with E2E coverage, handles edge cases properly (singular matrices), and passes all existing tests. The code is clean, well-documented, and performance-conscious. No logical errors or security concerns identified.
  • No files require special attention

Important Files Changed

Filename Overview
packages/framer-motion/src/gestures/drag/VisualElementDragControls.ts Added inverse transform matrix calculation to correct drag direction when parent elements are rotated or scaled. Implementation is mathematically sound.
dev/react/src/tests/drag-rotated-parent.tsx Test component with 180° rotated parent containing draggable child - clean and minimal test setup.
packages/framer-motion/cypress/integration/drag-rotated-parent.ts E2E test verifying draggable element follows cursor when parent is rotated 180° - includes clear before/after assertions.

Sequence Diagram

sequenceDiagram
    participant User
    participant PanSession
    participant DragControls as VisualElementDragControls
    participant Matrix as calcInverseParentMatrix
    participant MotionValue
    
    User->>PanSession: Pointer down (drag start)
    PanSession->>DragControls: onStart(event, info)
    DragControls->>Matrix: Calculate inverse matrix
    Matrix->>Matrix: Walk up parent tree
    Matrix->>Matrix: Accumulate transforms (rotate, scale)
    Matrix->>Matrix: Invert 2×2 matrix
    Matrix-->>DragControls: Return inverse matrix (cached)
    
    User->>PanSession: Pointer move (dragging)
    PanSession->>DragControls: onMove(event, info)
    Note over DragControls: info.offset in page space
    DragControls->>DragControls: applyInverseTransform(offset)
    Note over DragControls: Transform to parent's local space
    DragControls->>DragControls: updateAxis("x", correctedOffset)
    DragControls->>DragControls: updateAxis("y", correctedOffset)
    DragControls->>MotionValue: Set translateX/Y
    Note over MotionValue: Values now in correct coordinate system
    
    User->>PanSession: Pointer up (drag end)
    PanSession->>DragControls: onStop(event, info)
    DragControls->>DragControls: applyInverseTransform(velocity)
    DragControls->>DragControls: startAnimation(correctedVelocity)
    Note over DragControls: Momentum also uses corrected coordinates
Loading

Last reviewed commit: 47f84c0

Recompute the inverse parent matrix on every pointer move so the
correction stays accurate when the parent is animating (e.g. a
continuously rotating container).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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] Draggable direction is incorrect when parent container is rotated

1 participant