React-dnd: How do I get the coordinates of my dropped item relative to the drop target container?

Created on 13 May 2015  ·  15Comments  ·  Source: react-dnd/react-dnd

I am working with 1.0.0-rc.

In drag-around-naive:

const boxTarget = {
  drop(props, monitor, component) {
    const item = monitor.getItem();
    const delta = monitor.getDifferenceFromInitialOffset();
    const left = Math.round(item.left + delta.x);
    const top = Math.round(item.top + delta.y);

    component.moveBox(item.id, left, top);
  }
};

At present it looks like I need to get the offset from the page of the original item, then I can find the offset from the page of where I dropped, then I need to find the offset of the drop container from the page, then do that calculation and I have the relative position in my drop target container.

This seems like a lot of work for something pretty common...

enhancement

Most helpful comment

Yeah I guess we could have this for symmetry. For now, you can just use findDOMNode(component).getBoundingClientRect(), but I wouldn't mind adding this if more people voice support. Let's keep this open and see what others say.

All 15 comments

Seems like getClientOffset returns me the position relative to the viewport. So then all I have to do is find my offset in my drop target. Do you think its still worth having a convenience method relative to drop target?

Yeah I guess we could have this for symmetry. For now, you can just use findDOMNode(component).getBoundingClientRect(), but I wouldn't mind adding this if more people voice support. Let's keep this open and see what others say.

I would like something like this as well. I need to get the bounding rect/coordinates of the drop target, so when I hover something on it I can calculate if it's on the left, right, top or bottom side inside the drop target.

The relative coordinates can be useful for other use cases. For a Trello-like app, when you drag a card over a list, the list scrolls to bottom when hovering near bottom of drop area, and scrolls up when hovering near the top.

Trivial to implement on user space tho.

Also, I vaguely remember reading that accessing bounding rectangle triggers reflow in browsers and somewhat costly. Not sure if this is still the case.

I don't think there's a way to do this without actually accessing getBoundingClientRect because you never know if the item moved.

For a Trello-like app, when you drag a card over a list, the list scrolls to bottom when hovering near bottom of drop area, and scrolls up when hovering near the top.

That's a great use case.

The perf penalty is indeed, significant, and will be wasted if it's added to React DnD but your app doesn't use it. Therefore, I don't want to add this to React DnD.

It is easy to implement in userland, and you will be able to do things like throttle getBoundingClientRect calls, or put the related scrolling code into an animation frame.

I'm open to adding “scroll on hover at the bottom” _as an example_ to the sortable stress test. This would involve adding another drop target at the Container level that only listens to hover and scrolls inside an animation frame.

However I won't add the DOM coordinate methods to React DnD for the reasons described above.

@gaearon I'm interested in generalized solution. How can we use react-dnd-html5-backend instead of getBoundingClientRect directly?

I solved this by passing the DropTargetMonitor functions getInitialSourceClientOffset and getSourceClientOffset to a callback function inside my onDropevent. This way, my callback function can calculate the correct coordinates related to the view port.

// some code before...

const [, dropRef] = useDrop({
    accept: 'STICKY',
    drop(item, monitor) {
      move(
        item.id,
        monitor.getInitialSourceClientOffset(),
        monitor.getSourceClientOffset()
      );
    },
  });

// I'm updating my state with npm immer (immutable helper) throuthg the produce function...

const move = (id, initialPosition, finalPosition) => {
    setList(
      produce(list, draft => {
        list.map(item => {
          if (item.id === id) {
            item.position = getCorrectDroppedOffsetValue(
              initialPosition,
              finalPosition
            );
          }
          return item;
        });
      })
    );
  };

// and finally, my getCorrectDroppedOffsetValue function
const getCorrectDroppedOffsetValue = (initialPosition, finalPosition) => {
    // get the container (view port) position by react ref...
    const dropTargetPosition = ref.current.getBoundingClientRect();

    const { y: finalY, x: finalX } = finalPosition;
    const { y: initialY, x: initialX } = initialPosition;

    // calculate the correct position removing the viewport position.
   // finalY > initialY, I'm dragging down, otherwise, dragging up
    const newYposition =
      finalY > initialY
        ? initialY + (finalY - initialY) - dropTargetPosition.top
        : initialY - (initialY - finalY) - dropTargetPosition.top;

    const newXposition =
      finalX > initialX
        ? initialX + (finalX - initialX) - dropTargetPosition.left
        : initialX - (initialX - finalX) - dropTargetPosition.left;

    return {
      x: newXposition,
      y: newYposition,
    };
  };

findDOMNode does not work with functional component, and useDrop#drop does not pass any component in. Now if I want to get the ref of my component I have to store it separately and manually call the drop connector to pass it. Is there any more concise way to do this?

function useCustomDrop(onDrop: (info: unknown, xy: Point) => void) {

    const ref = useRef<Element>();

    const [, dropTarget] = useDrop<any, void, unknown>({
        accept: 'item-type',
        drop(item, monitor) {
            const offset = monitor.getSourceClientOffset();
            if (offset && ref.current) {
                const dropTargetXy = ref.current.getBoundingClientRect();
                onDrop(item.data, {
                    x: offset.x - dropTargetXy.left,
                    y: offset.y - dropTargetXy.top,
                });
            }
        }
    });

    return (elem) => {
        ref.current = elem;
        dropTarget(ref);
    };
}

@quanganhtran do you have any complete code samples you'd be willing share, please?

@dendrochronology

A contrived example: https://codesandbox.io/s/elastic-liskov-00ldf

The API is nicer when we don't have to store the ref ourselves (useGlobalDrop vs useLocalDrop). Now if only the ref, which I assume react-dnd already obtains, is attached ~to the monitor object~ somewhere (accessible in drop, I have no opinion where), it would resolves this.

this solution isn't reliable especially when you zoom in the browser or device resolution change. any idea?

i have the same question @sepehr09

Just passing by after a few google searches... Any update on the original question? (obtaining the x/y of a dropped item relative to the drop target)

I guess I'll join the list of those, who needs this. Any help please?

I think this feature very necessary, need to be add in library. Right now, Anyone worked with it, please help us !

Was this page helpful?
0 / 5 - 0 ratings