react-beautiful-dnd
react-beautiful-dnd copied to clipboard
Strange offset while dragging
Hey guys I need some help figuring out what's causing this strange behavior while dragging elements in this portal
modal component. I'll leave a demo video below. This problem occurs if I grab the elements from the bottom. In the console, there aren't any warnings or errors.
Expected behavior
Actual behavior
Steps to reproduce
Suggested solution?
What version of React
are you using?
^16.13.1
What version of react-beautiful-dnd
are you running?
Latest
What browser are you using?
Electron 8.2.5
Demo
I have the same problem, in a portal modal as well
I have the same issue with an extra unexpected behavior
To Produce the issue: https://codesandbox.io/s/olk8m320lq
Same issue here.
Maybe it's happening because the distant parent of the preview element has its position
CSS property set to fixed
or absolute
and the preview element's style
top
and left
aren't calculated as if they were starting from this parent's position, but rather from the window
or body
coordinates in the package. And CSS transforms the preview element starting from the parent's position.
I have the same problem in a fixed div.
I solved this by mutating the provided.draggableProps.style.left
and provided.draggableProps.style.right
values when provided.isDragging
is true
<Draggable >
{(provided, snapshot) => {
if (snapshot.isDragging) {
const offset = { x: 100, y: 100 } // your fixed container left/top position
const x = provided.draggableProps.style.left - offset.x;
const y = provided.draggableProps.style.top - offset.y;
provided.draggableProps.style.left = x;
provided.draggableProps.style.top = y;
}
return (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}>
YOUR ITEM
</div>
);
}}
</Draggable>
I solved this by mutating the
provided.draggableProps.style.left
andprovided.draggableProps.style.right
values whenprovided.isDragging
istrue
<Draggable > {(provided, snapshot) => { if (snapshot.isDragging) { const offset = { x: 100, y: 100 } // your fixed container left/top position const x = provided.draggableProps.style.left - offset.x; const y = provided.draggableProps.style.top - offset.y; provided.draggableProps.style.left = x; provided.draggableProps.style.top = y; } return ( <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}> YOUR ITEM </div> ); }} </Draggable>
Based on this, I changed the offset based in fixed position to offsetLeft,offsetTop and it works fine. Also, I don't know the position of my container (is a modal window the user can drag around the screen) so, at least in my case, I prefer to use this as follows:
<Draggable >
{(provided, snapshot) => {
if (snapshot.isDragging) {
provided.draggableProps.style.left = provided.draggableProps.style.offsetLeft;
provided.draggableProps.style.top = provided.draggableProps.style.offsetTop;
}
return (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}>
YOUR ITEM
</div>
);
}}
</Draggable>
EDIT:
It seems that provided.draggableProps.style.offsetLeft is not working since its value is undefined. I thought it was working okay because it really works with no warnings, but the value is undefined. Any ideas?
I'm having this same issue, and it only affects non-chromium browsers (Safari, Firefox). The solution that @camdagr8 proposed works great. However if your modal is scrollable, it will completely break. It will work fine if you are at the top of the window, but when you scroll down it will again reposition the draggables incorrectly
as found on another thread, I think a better solution is using a portal-- no need to know the offset:
let portal = document.createElement("div");
document.body.appendChild(portal);
function MyDraggableItem_render(props) {
let result = (
<div>[...]</div>
);
if (props.snapshot.isDragging) {
return ReactDOM.createPortal(result, portal);
}
return result;
}
[credit: @Venryx] https://github.com/atlassian/react-beautiful-dnd/issues/499#issuecomment-471334917
If anyone hits this thread, Just want to share my issue and solution. My project was fixed width and height with applied transform: translate(-50%, -50%)
as I needed to use top: 50%; left: 50%
to have it bang in the middle with fixed dimensions.
Obviously, I hit the offset issue, and my solution was to get that particular element offset divided in half (to cover transform
)
so this is the solution (inside component):
const viewportNode = document.getElementById('viewport') // container aligned in the middle
// In render part for Draggable
<Draggable index={Number(idx)} draggableId={String(idx)} key={idx}>
{(provided, snapshot) => {
if (snapshot.isDragging) {
provided.draggableProps.style.left = provided.draggableProps.style.left - (viewportNode.offsetLeft - (viewportNode.offsetWidth/2));
provided.draggableProps.style.top = provided.draggableProps.style.top - (viewportNode.offsetTop - (viewportNode.offsetHeight/2));
}
return ( // rest of your render )
}}
</Draggable>
Hope it helps if anyone comes across similar challange
{(provided, snapshot) => { if (snapshot.isDragging) { provided.draggableProps.style.left = provided.draggableProps.style.offsetLeft; provided.draggableProps.style.top = provided.draggableProps.style.offsetTop; } return ( YOUR ITEM); }}
Incase you experience this error:
TypeError: Cannot assign to read only property 'left' of object '#<Object>'
You change the code to:
<Draggable >
{(provided, snapshot) => {
if (snapshot.isDragging) {
provided.draggableProps.style = {
...provided.draggableProps.style,
left: provided.draggableProps.style.offsetLeft,
top: provided.draggableProps.style.offsetTop
}
}
return (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}>
YOUR ITEM
</div>
);
}}
</Draggable>
It also sorts out the issue of provided.draggableProps.style.offsetLeft being undefined. And this works for me!
For anyone who is having hard time implementing Portal with this This example of what portal with draggable item look like (Code only): https://codesandbox.io/s/admiring-joji-rgmve6?file=/src/DraggableTaskItem.jsx
Hey guys i manged to fix it quite easily and it works well for me. I wrote css class with important, this made my item drag start on his current position.
apply on draggable div:
left: auto !important; top: auto !important;
Thank you very much, I tried it and it worked for me
@Pawel1894 's solution worked for me until I needed to contain the draggables in scrollable divs with a fixed height.
I ended up fixing this by, instead of passing top: 'auto !important'
into the component's style, doing this:
- get a ref to the component that has the offset within your portal
- pass that ref to your Draggable item
- calculate the offset
const offset = modalRef.current?.getBoundingClientRect().top ?? 0
- conditionally apply the style when dragging
<Draggable
draggableId={myId}
index={index}
>
{(provided, snapshot) => {
const offset = modalRef.current?.getBoundingClientRect().top ?? 0
return (
<div
className="border border-gray-200 rounded-lg p-1.5 flex-1 bg-white"
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{
...provided.draggableProps.style,
left: 'auto !important',
top: snapshot.isDragging
? provided.draggableProps.style?.top - offset
: 'auto !important',
}}
>
modalRef is a ref for the div of the modal. Specifically it should be the div of the modal that sets the position the relative
This works for me, I used querySelector to get the id of the droppable then use the offsets
<Draggable >
{(provided, snapshot) => {
if (snapshot.isDragging) {
let offsetEl = document.querySelector(`[data-rbd-draggable-id='${provided.draggableProps["data-rbd-draggable-id"]}']`) as HTMLElement;
provided.draggableProps.style = {
...provided.draggableProps.style,
left: offsetEl ? offsetEl.offsetLeft : 0,
top: offsetEl ? offsetEl.offsetTop : 0
}
}
return (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}>
YOUR ITEM
</div>
);
}}
</Draggable>
@Pawel1894 's solution worked for me until I needed to contain the draggables in scrollable divs with a fixed height.
I ended up fixing this by, instead of passing
top: 'auto !important'
into the component's style, doing this:
- get a ref to the component that has the offset within your portal
- pass that ref to your Draggable item
- calculate the offset
const offset = modalRef.current?.getBoundingClientRect().top ?? 0
- conditionally apply the style when dragging
<Draggable draggableId={myId} index={index} > {(provided, snapshot) => { const offset = modalRef.current?.getBoundingClientRect().top ?? 0 return ( <div className="border border-gray-200 rounded-lg p-1.5 flex-1 bg-white" ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} style={{ ...provided.draggableProps.style, left: 'auto !important', top: snapshot.isDragging ? provided.draggableProps.style?.top - offset : 'auto !important', }} >
whats the modalRef?
whats the modalRef?
@IttzyTT sorry I should have clarified that!
modalRef
is a ref for the div of the modal. Specifically it should be the div of the modal that sets the position the relative.
I solved this by mutating the
provided.draggableProps.style.left
andprovided.draggableProps.style.right
values whenprovided.isDragging
istrue
<Draggable > {(provided, snapshot) => { if (snapshot.isDragging) { const offset = { x: 100, y: 100 } // your fixed container left/top position const x = provided.draggableProps.style.left - offset.x; const y = provided.draggableProps.style.top - offset.y; provided.draggableProps.style.left = x; provided.draggableProps.style.top = y; } return ( <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}> YOUR ITEM </div> ); }} </Draggable>
Based on this, I changed the offset based in fixed position to offsetLeft,offsetTop and it works fine. Also, I don't know the position of my container (is a modal window the user can drag around the screen) so, at least in my case, I prefer to use this as follows:
<Draggable > {(provided, snapshot) => { if (snapshot.isDragging) { provided.draggableProps.style.left = provided.draggableProps.style.offsetLeft; provided.draggableProps.style.top = provided.draggableProps.style.offsetTop; } return ( <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}> YOUR ITEM </div> ); }} </Draggable>
EDIT:
It seems that provided.draggableProps.style.offsetLeft is not working since its value is undefined. I thought it was working okay because it really works with no warnings, but the value is undefined. Any ideas?
thanks underium it works perfectly after finding many issue this was perfect solution thanks.
Hey guys i manged to fix it quite easily and it works well for me. I wrote css class with important, this made my item drag start on his current position.
apply on draggable div:
left: auto !important; top: auto !important;
I had the same problem and ended up needed to remove display:flex
from the droppable container.
I had this problem, could not fix it so I created a component that smoothly follows the cursor that I set at the same level in the hierarchy as DragDropContext In my case items are imgs but you could just as easily use other types of components, make sure you can easily set the props of the item to use the correct item props maybe using redux or useState
const ImageFollowCursor = ({ src }) => {
const mouseListener1 = (e) => {
const circle = document.getElementById('circle');
const left = e.clientX;
const top = e.clientY;
circle.style.transform = `translate(${left}px, ${top}px)`;
};
useEffect(() => {
document.addEventListener('mousemove', mouseListener1);
return () => {
document.removeEventListener('mousemove', mouseListener1);
};
}, [src]);
return (
<img
id="circle"
aria-label="cursor"
style={{
display: 'none',
objectFit: 'cover',
borderRadius: '10px',
opacity: 0.9,
position: 'fixed',
top: 0,
left: 0,
marginTop: '5px',
marginLeft: '5px',
width: '60px',
height: '60px',
pointerEvents: 'none',
zIndex: 999999,
transition: 'transform 0.1s',
transform: 'translate(0, 0)'
}}
/>
);
};
And I modified the handlers onDragUpdate and onDragEnd like so
const onDragUpdate = (result) => {
const circle = document.getElementById('circle');
if (circle) {
const draggingElement = mediaList.find((m) => m.id === result.draggableId);
circle.src = draggingElement?.get('thumbnailUrl');
circle.style.display = 'block';
}
// Rest of the handler...
}
const onDragEnd = (result) => {
const circle = document.getElementById('circle');
if (circle) {
circle.src = null;
circle.style.display = 'none';
}
// Rest of the handler...
}
I'm having this same issue, and it only affects non-chromium browsers (Safari, Firefox). The solution that @camdagr8 proposed works great. However if your modal is scrollable, it will completely break. It will work fine if you are at the top of the window, but when you scroll down it will again reposition the draggables incorrectly
I there any way so that it will work for scrollable container?
Same problem inside a scrollable modal and none of the above solutions work
I have the same issue with an extra unexpected behavior
To Produce the issue: https://codesandbox.io/s/olk8m320lq
This happens because your modal has a div with position
= relative
, absolute
or fixed
that has a positioning style like margin: auto
, or anything that manages it's placement on screen.
You should make this div fullscreen and manage the placement of your dialog by creating another div with static position and place it anywhere you want using flex or etc...
Hey guys I need some help figuring out what's causing this strange behavior while dragging elements in this
portal
modal component. I'll leave a demo video below. This problem occurs if I grab the elements from the bottom. In the console, there aren't any warnings or errors.Expected behavior
Actual behavior
Steps to reproduce
Suggested solution?
What version of
React
are you using?^16.13.1
What version of
react-beautiful-dnd
are you running?Latest
What browser are you using?
Electron 8.2.5
Demo
https://imgur.com/a/Uxyiwgl
And this happens because your modal is scrollable. There is no way to implement this library for two scrollable layouts inside each other right now, and this is mentioned in the official documentation of react beautiful dnd.
The only way is to make your modal un-scrollable, and instead set overflow-y: auto
and max-height: x
for the droppable section in your modal.