Draggable items are positioned incorrectly when parent of Droppable has `transform`
72403 opened this issue · 6 comments
Expected behavior
If I place a Droppable and Draggables in a div and add a transform to that div (e.g. transform="translateX(0%) translateY(0px) translateZ(0px)"
), Draggables should follow my mouse as I drag them.
Actual behavior
Items being dragged positioned way to the right. I originally thought they were disappearing when dragging, but if I drag my cursor way to the left they'll appear on the right side of the screen.
Steps to reproduce
- https://codesandbox.io/s/reverent-lucy-eibotf
- Drag an item around and it will disappear/be placed way to the right.
Suggested solution?
Not sure.
What version of React
are you using?
18.2.0
What version of @hello-pangea/dnd
are you running?
16.2.0
What browser are you using?
The issue appears in Firefox and Safari. I haven't tested Chrome.
Demo
I have the same issue.
When it's inside Modal (I'm using Mantine Modal), it's not positioned properly when it is dragging.
@coolneo4u @72403 Had the same issue. Went through the docs and found this:
Warning:
position: fixed
@hello-pangea/dnd
usesposition: fixed
to position the dragging element. This is quite robust and allows for you to haveposition: relative | absolute | fixed
parents. However, unfortunatelyposition:fixed
is impacted bytransform
(such astransform: rotate(10deg);
). This means that if you have atransform: *
on one of the parents of a<Draggable />
then the positioning logic will be incorrect while dragging. Lame! For most consumers this will not be an issue.
To get around this you can reparent your<Draggable />
. We do not enable this functionality by default as it has performance problems.
This fixed the issue for me.
Perhaps we could do this when drag start?
const TRANSFORM_DEFAULT_VALUES = ['none', 'initial', 'inherit', 'unset'];
const WILL_CHANGE_DEFAULT_VALUES = ['auto', 'initial', 'inherit', 'unset'];
export function getTransformedParentCoords(element: Element) {
let parentNode = element.parentNode;
while (parentNode !== null) {
if (isHTMLElement(parentNode)) {
const { transform, willChange } = getComputedStyle(parentNode);
if (
!TRANSFORM_DEFAULT_VALUES.includes(transform) ||
!WILL_CHANGE_DEFAULT_VALUES.includes(willChange)
) {
const { x, y } = parentNode.getBoundingClientRect();
return { x, y };
}
}
parentNode = parentNode.parentNode;
}
return { x: 0, y: 0 };
}
export const getBoundingClientRect = (element: Element, isFixedStrategy = false) => {
const clientRect = element.getBoundingClientRect();
let offsetX = 0;
let offsetY = 0;
if (isFixedStrategy) {
const { x, y } = getTransformedParentCoords(element);
offsetX = x;
offsetY = y;
}
return DOMRect.fromRect({
x: clientRect.left - offsetX,
y: clientRect.top - offsetY,
width: clientRect.width,
height: clientRect.height,
});
};
Perhaps we could do this when drag start?
const TRANSFORM_DEFAULT_VALUES = ['none', 'initial', 'inherit', 'unset']; const WILL_CHANGE_DEFAULT_VALUES = ['auto', 'initial', 'inherit', 'unset']; export function getTransformedParentCoords(element: Element) { let parentNode = element.parentNode; while (parentNode !== null) { if (isHTMLElement(parentNode)) { const { transform, willChange } = getComputedStyle(parentNode); if ( !TRANSFORM_DEFAULT_VALUES.includes(transform) || !WILL_CHANGE_DEFAULT_VALUES.includes(willChange) ) { const { x, y } = parentNode.getBoundingClientRect(); return { x, y }; } } parentNode = parentNode.parentNode; } return { x: 0, y: 0 }; } export const getBoundingClientRect = (element: Element, isFixedStrategy = false) => { const clientRect = element.getBoundingClientRect(); let offsetX = 0; let offsetY = 0; if (isFixedStrategy) { const { x, y } = getTransformedParentCoords(element); offsetX = x; offsetY = y; } return DOMRect.fromRect({ x: clientRect.left - offsetX, y: clientRect.top - offsetY, width: clientRect.width, height: clientRect.height, }); };
I'm trying to understand this. So we would call getBoundingClientRect
in the onDragStart of the DragDropContext passing it an element? What specifically gets passed as the element?
I also have this problem while doing dnd inside a radix Dropdown menu. A temporary fix was to overwrite top
and left
style properties of Draggable
to unset
. But this still presents problems if you try to drag an item from one list to the other in a vertical placement. What a headache.
Perhaps we could do this when drag start?
const TRANSFORM_DEFAULT_VALUES = ['none', 'initial', 'inherit', 'unset']; const WILL_CHANGE_DEFAULT_VALUES = ['auto', 'initial', 'inherit', 'unset']; export function getTransformedParentCoords(element: Element) { let parentNode = element.parentNode; while (parentNode !== null) { if (isHTMLElement(parentNode)) { const { transform, willChange } = getComputedStyle(parentNode); if ( !TRANSFORM_DEFAULT_VALUES.includes(transform) || !WILL_CHANGE_DEFAULT_VALUES.includes(willChange) ) { const { x, y } = parentNode.getBoundingClientRect(); return { x, y }; } } parentNode = parentNode.parentNode; } return { x: 0, y: 0 }; } export const getBoundingClientRect = (element: Element, isFixedStrategy = false) => { const clientRect = element.getBoundingClientRect(); let offsetX = 0; let offsetY = 0; if (isFixedStrategy) { const { x, y } = getTransformedParentCoords(element); offsetX = x; offsetY = y; } return DOMRect.fromRect({ x: clientRect.left - offsetX, y: clientRect.top - offsetY, width: clientRect.width, height: clientRect.height, }); };I'm trying to understand this. So we would call
getBoundingClientRect
in the onDragStart of the DragDropContext passing it an element? What specifically gets passed as the element?
We should pass dragging element and call getBoundingClientRect
when change dragging element coords.