Search code examples
reactjsreact-hooksdraggabledroppablednd-kit

How can I calculate draggable position independently from a scrollable droppable area in React using dnd-kit?


I created a touch-screen application using React. I am using the dnd-kit library to enable the dragging & dropping of certain divs.

I have a calendar where the days are the columns, and the rows are categories. Each cell is a Droppable area. Draggable items with different lengths are stored in 3 separate containers. I can drag these items on the grid and place them in a specific cell. So far everything works fine.

Now my issue: My calendar grid is wider than the screen, thus I set "overflow-y" to "auto". Now I can scroll and bring the part I want to view into focus. The problem is that when I now drag one of my Draggable elements unto the calendar grid, the calculated position of the Draggable element is offset by the scroll amount. If I scroll 50px to the right, then the Draggable is also 50px right from the position where I am dragging at.

Is there a way to correct this? To ignore the scrolled amount when dragging the Draggable element over the calendar with the Droppable cells? Or to overwrite the default transform attribute to account for this offset (only when over the calendar).

Here is my Draggable element

` import {useDraggable} from '@dnd-kit/core'; import styled from "styled-components";

const StyledDraggable = styled.div`
  z-index: 2;
  transform: ${props => props.transform};
  position: ${props => props.position};
  touch-action: manipulation;
`;

export function Draggable(props) {
    const {attributes, listeners, setNodeRef, transform, isDragging} = useDraggable({
        id: props.id,
        data: {
            type: 'draggable',
            droppableId: props.id,
            date: props.startDate
        }
    });

    const tForm = transform ?
        `translate3d(${transform.x}px, ${transform.y}px, 0)` : undefined;

    const position = isDragging ? 'absolute' : null;

    return (
        <StyledDraggable ref={setNodeRef} transform={tForm} position={position} {...listeners} {...attributes}>
            {props.children}
        </StyledDraggable>
    );
}

`

I already tried to somehow calculate the scroll amount of the calendar div when hovering over, and then manually changing the transform style property of the draggable element, but without any success: ` function handleDragMove(event) { ...

    let calendar = document.querySelector("#scrollContainer")
    let draggableElement = document.getElementById(active.id).parentElement
    let transformation = window.getComputedStyle(draggableOrder, null).transform
    let xOffset = parseInt(transformation.split(",")[4])
    let yOffset = parseInt(transformation.split(",")[5])
    draggableElement.style.transform = "translate3d(" + (xOffset - calendar.scrollLeft) + "px, " + yOffset + "px, 0)"
}

`


Solution

  • So I kind of found a solution. I'm sure there is a more elegant and built in solution, but I found a workaround:

    I get the child element of my draggable div, and manually override the left offset. To calculate the offset, I use the positions of the calendar div, the draggable order div (my original div-pool of multiple draggable elements) and the event itself (+ a fix offset)

    Here is how I calculate the position:

    handleDragMove(event) {
        ...
        const calendarRect = document.querySelector("#innerCalendar").getBoundingClientRect();
        const draggableOrder = document.getElementById(active.id).parentElement.getBoundingClientRect();
        const dropBoxRect = document.getElementById(active.data.current.container).getBoundingClientRect();
        const x = event.delta.x + calendarRect.left - draggableOrder.left + dropBoxRect.left - 250;
        document.getElementById(active.id).style.left = x + "px";
    }
    

    It works (takes a fraction of a second to calculate), but I would still love to have a cleaner preferably built-in solution.