I am trying to create a drag and drop feature that is a child component within a modal where the currently dragged item follows the position of the mouse, however, the position of the mouse is being offset by other content in the modal not part of the drag and drop component. Here is a repl showing the issue I would like for the element to follow the cursor exactly.
Showing the relation between cursor position and modal content
The basic element structure is as follows:
<Main>
<Modal>
<h2>Modal Header</h2>
<DndComponent>
<div>Drag Me</div>
<div>Element that follows cursor</div>
</DndComponent>
</Modal>
</Main>
I have tried different combinations of e.clientY
, e.offsetY
, and e.pageY
in combination with getBoundingClientRect
from the component container. I would prefer to not put a ref
on the modal as the drag and drop feature will be used in many different places. I have also tried using both position: absolute
and position: fixed
Consider applying position: absolute
to the dragitem
. This ensures the "start" position of the element is relative to the boundsRef
element, which is where the mousePos
is calculated relative to.
const { useState, useRef } = React;
function App() {
const [isDragging, setIsDragging] = useState(false);
const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
const boundsRef = useRef(null);
const handleMouseDown = () => {
setIsDragging(true);
};
const handleMouseMove = (e) => {
const bounds = boundsRef.current.getBoundingClientRect();
if (isDragging) {
setMousePos({ x: e.clientX - bounds.x, y: e.clientY - bounds.y });
}
};
return (
<main>
<div className="container" onPointerMove={handleMouseMove}>
<div className="modal">
<h3>Modal Header</h3>
<h4 style={{ marginBottom: "20px" }}>Sub Header</h4>
{/* start of child component */}
<div ref={boundsRef} style={{ position: "relative" }}>
<ul>
<li className="modal-content" onPointerDown={handleMouseDown}>
<p>Drag Me</p>
</li>
</ul>
{isDragging && (
<div
className="dragItem"
style={{
transform: `translate(${mousePos.x}px, ${mousePos.y}px)`,
}}
>
<p>Item Being Dragged</p>
</div>
)}
</div>
{/* end of child component */}
</div>
</div>
</main>
);
}
ReactDOM.createRoot(document.getElementById("app")).render(<App />);
* {
margin: 0;
padding: 0;
}
.container {
position: relative;
height: 100vh;
width: 100vw;
background-color:#fff;
}
.modal {
height: 50vh;
width: 50vw;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #ededed;
}
.modal-content {
user-select: none;
}
.modal-content:hover {
cursor: grab;
}
.dragItem {
position: absolute;
z-index: 1000;
left: 0;
top: 0;
pointer-events: none;
background-color: #8593ff24;
padding: 0.5rem 1rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js" integrity="sha512-8Q6Y9XnTbOE+JNvjBQwJ2H8S+UV4uA6hiRykhdtIyDYZ2TprdNmWOUaKdGzOhyr4dCyk287OejbPvwl7lrfqrQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js" integrity="sha512-MOCpqoRoisCTwJ8vQQiciZv0qcpROCidek3GTFS6KTk2+y7munJIlKCVkFCYY+p3ErYFXCjmFjnfTTRSC1OHWQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<div id="app"></div>