Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How do I get the coordinates of my dropped item relative to the drop target container? #151

Closed
vjpr opened this issue May 13, 2015 · 22 comments

Comments

@vjpr
Copy link

vjpr commented May 13, 2015

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...

@vjpr
Copy link
Author

vjpr commented May 13, 2015

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?

@gaearon
Copy link
Member

gaearon commented May 13, 2015

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.

@robertbakker
Copy link

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.

@hakanderyal
Copy link
Collaborator

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.

@gaearon
Copy link
Member

gaearon commented May 22, 2015

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 gaearon closed this as completed May 22, 2015
@sheerun
Copy link

sheerun commented Jan 24, 2016

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

@darde
Copy link

darde commented Sep 2, 2019

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,
    };
  };

@quanganhtran
Copy link

quanganhtran commented Sep 19, 2019

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);
    };
}

@dendrochronology
Copy link

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

@quanganhtran
Copy link

quanganhtran commented Apr 15, 2020

@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.

@sepehr09
Copy link

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

@hungdev
Copy link

hungdev commented Jul 2, 2020

i have the same question @sepehr09

@schester44
Copy link

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)

@NerveClasp
Copy link

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

@minhdongsss
Copy link

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

@nfplay
Copy link

nfplay commented May 15, 2021

I too would benefit of a solution to this problem! Thanks

@JanMisker
Copy link

JanMisker commented Jul 28, 2021

My workaround is to put a div with element reference around the drop target and use that to measure. Because I also put other stuff inside that container div I position the drop target absolute over the entire container.

Something like this (pseudo-code, copy-paste at your own risk)

const containerRef = useRef<HTMLDivElement | null>();
const [, drop] = useDrop({
    drop: (item, monitor) => {
        console.log(
           containerRef.current.getBoundingClientRect(), 
           monitor.getClientOffset())
        )
    }
    // etcetera
});

return (<div ref={containerRef} style={{postion:'relative'}}>
  <div ref={drop} style={{
     position:'absolute',top:0,left:0,width:'100%',height:'100%'
  }}></div>
</div>)

@JanKaram2020
Copy link

do you have any complete code samples you can share, please?

@dherault
Copy link

Here is a working solution on hover:

const [, drop] = useDrop(() => ({
  accept: 'Box',
  hover: (_item, monitor) => {
    const offset = monitor.getClientOffset()

    if (offset && rootRef.current) {
      const rect = rootRef.current.getBoundingClientRect()
      console.log({
        x: offset.x - rect.left,
        y: offset.y - rect.top,
      })
    }
  },
  drop: (_item, monitor) => {
    if (monitor.didDrop()) return

    return {
      id: 'foo',
    }
  },
  collect: () => ({}),
}), [])

Hope it helps.

@fachu000
Copy link

Thanks @dherault . How do you get rootRef?

@dherault
Copy link

Here's a gist:

const rootRef = useRef()

// ... the code from before

const forkedRef = useForkedRef(rootRef, drop) // Google useForkedRef

return <div ref={forkedRef} />

@fachu000
Copy link

Thanks @dherault . This is another way of doing it more explicitly, in case somebody is interested:

function DropTarget(props) {

    const boundingBox = useRef(null)

    const [output_of_collect, drop_ref] = useDrop(
        () => ({
            // ... <your stuff> ... //
            hover: (item, monitor) => {
                const offset = monitor.getClientOffset();
                const deltaX = offset.x - boundingBox.current.x
                const deltaY = offset.y - boundingBox.current.y
                console.log("deltaX = " + deltaX + ", deltaY = " + deltaY)
            },
            // ... < your other stuff > ... // 
        }))

    function combinedRef(el) {
        drop_ref(el);
        if (el) {
            boundingBox.current = el.getBoundingClientRect();
        }
    }

    return (
        <div ref={combinedRef} > Content of your div </div>
    )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests