Skip to content

Commit

Permalink
Merge pull request #2707 from framer/fix/drag-constraints
Browse files Browse the repository at this point in the history
Fixing `dragConstraints={ref}` with active layout animations
  • Loading branch information
mergetron[bot] committed Jun 25, 2024
2 parents 62d0d0a + a67bfdf commit 98833d2
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 40 deletions.
54 changes: 42 additions & 12 deletions dev/react/src/examples/Drag-constraints-ref.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useRef, useState } from "react"
import { useEffect, useRef, useState } from "react"
import { motion } from "framer-motion"

const container = {
Expand All @@ -18,20 +18,50 @@ const child = {
borderRadius: 20,
}

/**
* This sibling layout animation is designed to fuzz/stress the drag constraints
* measurements. Remeasuring the constraints during drag would previously mess
* up the position of the draggable element.
*/
const SiblingLayoutAnimation = () => {
const [state, setState] = useState(false)

useEffect(() => {
const timer = setTimeout(() => setState(!state), 500)

return () => clearTimeout(timer)
}, [state])

return (
<motion.div
layout
style={{
...child,
background: "blue",
position: "relative",
left: state ? "100px" : "0",
}}
/>
)
}

export const App = () => {
const ref = useRef()
const [count, setCount] = useState(0)
return (
<div ref={ref} style={container}>
<motion.div
drag
//dragElastic
dragConstraints={ref}
whileTap={{ scale: 0.95 }}
whileHover={{ scale: 1.1 }}
style={child}
onClick={() => setCount(count + 1)}
/>
</div>
<>
<div ref={ref} style={container}>
<motion.div
drag
dragConstraints={ref}
whileTap={{ scale: 0.95 }}
whileHover={{ scale: 1.1 }}
style={child}
onClick={() => setCount(count + 1)}
id="draggable"
/>
</div>
<SiblingLayoutAnimation />
</>
)
}
82 changes: 57 additions & 25 deletions dev/react/src/tests/drag-ref-constraints.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { motion, useMotionValue } from "framer-motion"
import { useRef, useState, useLayoutEffect } from "react";
import { useRef, useState, useLayoutEffect, useEffect } from "react"

// It's important for this test to only trigger a single rerender while dragging (in response to onDragStart) of draggable component.

Expand All @@ -16,30 +16,62 @@ export const App = () => {
const x = useMotionValue("100%")

return (
<div style={{ height: 2000, paddingTop: 100 }}>
<motion.div
data-testid="constraint"
style={{ width: 200, height: 200, background: "blue" }}
ref={containerRef}
>
<>
<div style={{ height: 2000, paddingTop: 100 }}>
<motion.div
id="box"
data-testid="draggable"
drag
dragElastic={0}
dragMomentum={false}
style={{
width: 50,
height: 50,
background: dragging ? "yellow" : "red",
x,
}}
dragConstraints={containerRef}
layout={layout}
onDragStart={() => setDragging(true)}
onDragEnd={() => setDragging(false)}
/>
</motion.div>
</div>
data-testid="constraint"
style={{ width: 200, height: 200, background: "blue" }}
ref={containerRef}
>
<motion.div
id="box"
data-testid="draggable"
drag
dragElastic={0}
dragMomentum={false}
style={{
width: 50,
height: 50,
background: dragging ? "yellow" : "red",
x,
}}
dragConstraints={containerRef}
layout={layout}
onDragStart={() => setDragging(true)}
onDragEnd={() => setDragging(false)}
/>
</motion.div>
</div>
<SiblingLayoutAnimation />
</>
)
}

/**
* This sibling layout animation is designed to fuzz/stress the drag constraints
* measurements. Remeasuring the constraints during drag would previously mess
* up the position of the draggable element.
*/
const SiblingLayoutAnimation = () => {
const [state, setState] = useState(false)

useEffect(() => {
const timer = setTimeout(() => setState(!state), 200)

return () => clearTimeout(timer)
}, [state])

return (
<motion.div
layout
style={{
width: 200,
height: 200,
borderRadius: 20,
background: "blue",
position: "relative",
left: state ? "100px" : "0",
}}
/>
)
}
4 changes: 2 additions & 2 deletions packages/framer-motion/cypress/integration/drag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,9 +234,9 @@ describe("Drag", () => {
.get("[data-testid='draggable']")
.trigger("pointerdown", 10, 10)
.trigger("pointermove", 15, 15)
.wait(50)
.wait(200)
.trigger("pointermove", 300, 300, { force: true })
.wait(50)
.wait(200)
.trigger("pointerup", { force: true })
.should(($draggable: any) => {
const draggable = $draggable[0] as HTMLDivElement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -872,8 +872,11 @@ export function createProjectionNode<I>({

resetTransform() {
if (!resetTransform) return

const isResetRequested =
this.isLayoutDirty || this.shouldResetTransform
this.isLayoutDirty ||
this.shouldResetTransform ||
this.options.alwaysMeasureLayout

const hasProjection =
this.projectionDelta && !isDeltaZero(this.projectionDelta)
Expand Down

0 comments on commit 98833d2

Please sign in to comment.