diff --git a/CHANGELOG.md b/CHANGELOG.md
index 684f3b77e2..fba6d4a41b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
Framer Motion adheres to [Semantic Versioning](http://semver.org/).
+## [5.0.2] 2021-11-02
+
+- Convert x/y from percent to pixels before drag. [Issue](https://github.com/framer/motion/issues/424)
+
## [5.0.1] 2021-11-01
### Added
diff --git a/cypress/integration/drag.ts b/cypress/integration/drag.ts
index 4f03aaa38a..11ea5af826 100644
--- a/cypress/integration/drag.ts
+++ b/cypress/integration/drag.ts
@@ -50,6 +50,29 @@ describe("Drag", () => {
})
})
+ it("Drags the element by the defined distance with percentage initial offset", () => {
+ cy.visit("?test=drag&x=200%&y=200%")
+ .get("[data-testid='draggable']")
+ .wait(200)
+ .trigger("pointerdown", 5, 5)
+ .trigger("pointermove", 10, 10) // Gesture will start from first move past threshold
+ .wait(50)
+ .trigger("pointermove", 200, 300, { force: true })
+ .wait(50)
+ .trigger("pointerup", { force: true })
+ .should(($draggable: any) => {
+ const draggable = $draggable[0] as HTMLDivElement
+ const { left, top } = draggable.getBoundingClientRect()
+
+ expect(left).to.equal(300)
+
+ // TODO: This should actually be 400, but for some reason the test scroll
+ // scrolls an additional 100px when dragging starts. But this has been manually verified
+ // as working
+ expect(top).to.equal(300)
+ })
+ })
+
it("Locks drag to x", () => {
cy.visit("?test=drag&axis=x")
.get("[data-testid='draggable']")
diff --git a/dev/tests/drag-ref-constraints.tsx b/dev/tests/drag-ref-constraints.tsx
index 4821bba787..05a2d28fc2 100644
--- a/dev/tests/drag-ref-constraints.tsx
+++ b/dev/tests/drag-ref-constraints.tsx
@@ -1,4 +1,4 @@
-import { motion } from "@framer"
+import { motion, useMotionValue } from "@framer"
import * as React from "react"
// It's important for this test to only trigger a single rerender while dragging (in response to onDragStart) of draggable component.
@@ -13,7 +13,7 @@ export const App = () => {
React.useLayoutEffect(() => {
window.scrollTo(0, 100)
}, [])
-
+ const x = useMotionValue("100%")
return (
{
width: 50,
height: 50,
background: dragging ? "yellow" : "red",
+ x,
}}
dragConstraints={containerRef}
layout={layout}
diff --git a/dev/tests/drag.tsx b/dev/tests/drag.tsx
index 5633c7d771..fb99eb0fca 100644
--- a/dev/tests/drag.tsx
+++ b/dev/tests/drag.tsx
@@ -1,8 +1,17 @@
import { motion } from "@framer"
import * as React from "react"
-// It's important for this test to only trigger a single rerender while dragging (in response to onDragStart) of draggable component.
+function getValueParam(params: any, name: string) {
+ const param = params.get(name) as string | undefined
+ if (!param) return 0
+ if (param.endsWith("%")) {
+ return param
+ } else {
+ return parseFloat(param)
+ }
+}
+// It's important for this test to only trigger a single rerender while dragging (in response to onDragStart) of draggable component.
export const App = () => {
const params = new URLSearchParams(window.location.search)
const axis = params.get("axis")
@@ -12,8 +21,8 @@ export const App = () => {
const right = parseFloat(params.get("right")) || undefined
const bottom = parseFloat(params.get("bottom")) || undefined
const snapToOrigin = Boolean(params.get("return"))
- const x = parseFloat(params.get("x")) || 0
- const y = parseFloat(params.get("y")) || 0
+ const x = getValueParam(params, "x")
+ const y = getValueParam(params, "y")
const layout = params.get("layout") || undefined
// We do this to test when scroll position isn't 0/0
diff --git a/package.json b/package.json
index 0b6c494976..74cee88c70 100644
--- a/package.json
+++ b/package.json
@@ -159,7 +159,7 @@
},
{
"path": "./dist/size-webpack-dom-max.js",
- "maxSize": "30.2 kB"
+ "maxSize": "30.3 kB"
}
]
}
diff --git a/src/gestures/drag/VisualElementDragControls.ts b/src/gestures/drag/VisualElementDragControls.ts
index 17aaa1d4f5..429c80029f 100644
--- a/src/gestures/drag/VisualElementDragControls.ts
+++ b/src/gestures/drag/VisualElementDragControls.ts
@@ -30,6 +30,8 @@ import {
import { LayoutUpdateData } from "../../projection/node/types"
import { addDomEvent } from "../../events/use-dom-event"
import { mix } from "popmotion"
+import { percent } from "style-value-types"
+import { calcLength } from "../../projection/geometry/delta-calc"
export const elementDragControls = new WeakMap<
VisualElement,
@@ -125,7 +127,22 @@ export class VisualElementDragControls {
* Record gesture origin
*/
eachAxis((axis) => {
- this.originPoint[axis] = this.getAxisMotionValue(axis).get()
+ let current = this.getAxisMotionValue(axis).get() || 0
+
+ /**
+ * If the MotionValue is a percentage value convert to px
+ */
+ if (percent.test(current)) {
+ const measuredAxis =
+ this.visualElement.projection?.layout?.actual[axis]
+
+ if (measuredAxis) {
+ const length = calcLength(measuredAxis)
+ current = length * (parseFloat(current) / 100)
+ }
+ }
+
+ this.originPoint[axis] = current
})
// Fire onDragStart event