react-rnd icon indicating copy to clipboard operation
react-rnd copied to clipboard

enhancement: don't allow overlap

Open dexluce opened this issue 8 years ago • 14 comments

Hello,

I see a few way to restrict the overlaping of two rnd elements, but it would be nice to just set a boolean for that.

As I need this, could you tell me how you would do that? (Do I use onDrag callback to verify if overlap is happening and prevent it?)

Thank you very much for the nice work, regards

dexluce avatar Mar 31 '17 16:03 dexluce

That would be really nice, actually :)

alexdemartos avatar Apr 10 '17 15:04 alexdemartos

Well, I wasn't able to do it by myself. onDrag and onResize doesn't allow the use of any function on the rnd element. For exemple, this.rnd.updatePosition(... ) won't work in onDrag or onResize. Changing "bounds" doesn't works neither.

So... I change my approach. I have implemented a 'magnet' onResizeStop and onDragStop. I juste write quickly the psedo-code. Magnet is only apply on X axe. I don't have the time know but let my know and I'll write a more usable code then this:

var allDivBorderYouWantToMagnetize = {}
var magneticPos;

onDragStart(() => {
  //here, you want to keep all x magnetic value as key in an object.
  forEach(rnd in rndElements) {
    this.allDivBorderYouWantToMagnetize[rnd.xPosition].push(rnd)
  }
})
onDrag((rnd) => {
  Object.keys(this.allDivBorderYouWantToMagnetize).forEach((key) => {
    //magnet will be activated if you are 5px around another rnd element
    if (rnd.x - key > -5 && rnd.x - key < 5)
      this.magneticPos = key;
    else
      this.magneticPos = event.xPos;
  })
})
onDragStop(() => {
  this.rnd.setPosition({x: this.magneticPos})
})

dexluce avatar Apr 10 '17 16:04 dexluce

With that approach, the object only gets moved into its place after you've finished dragging. I'm trying to accomplish the same thing, but also while you're dragging the object (no luck yet).

I would need something like "return false" on "onDrag" to cancel the movement, or a way to access the React object inside "onDrag" to call setPosition if the overlap condition is met.

alexdemartos avatar May 10 '17 11:05 alexdemartos

Hi guys, any updates on this?

subeax avatar Sep 12 '17 18:09 subeax

您好,我想问一下防止rnd重叠现在有实现吗

pingbaohua avatar Sep 03 '18 03:09 pingbaohua

Hello! Did anyone find a way to solve this problem? I'm in need of the exact same thing

arthurepc avatar Feb 04 '19 19:02 arthurepc

Hi, here is my proposal; for 2 Rnds for now - some issue with more than 2 - i suspect issue with the loop. https://codesandbox.io/s/react-rnd-test-lcp7b

Call this method whenever you need to check for collision. In my demo im running it when onDragStop( ) and onResizeStop( ) are triggered.

 function overlaps() {
    //get all draggables in the doc - should be done using refs etc in react  
    var draggables = document.getElementsByClassName("react-draggable");

   //iterate through all of them checking overlap.
    Array.from(draggables).map((item, e) => {
      Array.from(draggables).map((oItem, e) => {

        if (item !== oItem) {
          const rect1 = item.getBoundingClientRect();
          const rect2 = oItem.getBoundingClientRect();

          const isInHoriztonalBounds = rect1.x < rect2.x + rect2.width && rect1.x + rect1.width > rect2.x;
          const isInVerticalBounds = rect1.y < rect2.y + rect2.height && rect1.y + rect1.height > rect2.y;


          var isOverlapping = isInHoriztonalBounds && isInVerticalBounds;
          
          //right here you know the two components that are overlapping
          //resolve overlap the way best fits your project.
          
          //setting state.
          setCollision(isOverlapping);
        }
      });
    });
  }

davvit avatar Jan 31 '21 06:01 davvit

This code handles collision detection and restoration to the last known "safe point" or default position when drag ends (if there is a collison of the elements).

/* This will handle our overlap calculations
In my code, I have this above component that handles single node */
function haveIntersection(other, main) {
	return !(
		main.x > other.x + other.width ||
		main.x + main.width < other.x ||
		main.y > other.y + other.height ||
		main.y + main.height < other.y
	);
}
/*
* mn872 is a class of each nested div, that holds text / icons
* Structure looks like this map > div.wrap > Rnd > div.wrap2 > div.mn872 > .... (simplified)
* We have to use .some() as it is not possible to stop forEach loop
* We need to stop loop, because next element would override the state to false
* You can also use .every(), but in that case you will flip return values
* setIsCollision() is just regular useState(bool)
*/
const [isCollision, setIsCollision] = useState(false);
function handleOverlap(node, xy) {
	const main = node?.querySelector(".mn872"); // current dragged or resized node
	const targetRect = main?.getBoundingClientRect();
	[...document.querySelectorAll(".mn872")].some((group) => {
		if (group === main) return; // continue with a loop if the current element is inside the group
		if (haveIntersection(group.getBoundingClientRect(), targetRect)) {
			setIsCollision(true);
			return true; // current element is overlapping - stop loop
		}
               setIsCollision(false);
               setSafePoint(xy); // remove this line if you want to snap to initial position
               return;  // continue with a loop - current element is NOT overlapping
	});
}
const [safePoint, setSafePoint] = useState({ x: 0, y: 0 });
function handleDragStart(e, { x, y }) {
	setSafePoint({ x, y });
}
/* We use 1ms timeout as browser needs a tiny bit of time to have everything in sync.
On the other hand, we need to have correct values. Try it without timeout and you will see.
I call this function from Rnd component during "onDrag" event. It should work for other events too. */
function handleDrag(e, { node, x, y }) {
	setTimeout(() => handleOverlap(node, { x, y }), 1);
}
function handleDragStop(e, { node: { clientWidth } }) {
	if (isCollision) {
		nodeRef.current.updatePosition(safePoint);
		setIsCollision(false);
		return;
	}

	//const width = clientWidth;
	//nodeRef.current.updateSize({ width, height: TL_DEFUALT_HEIGHT });
        const { x, y } = nodeRef.current.draggable.state;
        nodeRef.current.updatePosition({ x, y });
}

Some parts token from https://konvajs.org/docs/sandbox/Collision_Detection.html

radosek avatar Jul 20 '22 07:07 radosek

@Rados51 can you help with an example on codesandbox or anything else that would help out a newbie on react and react-rnd

sandeepzgk avatar Feb 02 '23 23:02 sandeepzgk

@sandeepzgk

This is the most basic example (check console.log on collision) https://codesandbox.io/s/react-rnd-collision-example-4impqy?file=/src/index.js

If you need material to start working with React, I would recommend Scrimba

radosek avatar Feb 02 '23 23:02 radosek

I would also really love this to be implemented.

nicollegah avatar Mar 08 '23 15:03 nicollegah

I second that it'd be great to extend the bounds prop to disallow overlapping

carterjfulcher avatar Mar 23 '23 21:03 carterjfulcher