Vanilla JS Div Collision Detection
Asked Answered
A

2

1

My implementation of the following can be found on jsfiddle.net

I have four divs. My goal is to make them draggable around the page but NOT to allow them to overlap one another. Each can be dragged around the page with a mousemove listener.

container.addEventListener('mousemove',mouseMove);
  function mouseMove(e) {
    if (!mouseDown) {return;}
    let coords=e.target.getBoundingClientRect();
    let movX=e.movementX;
    let movY=e.movementY;
    if (!collision(movX,movY,e.target.classList[1],coords)){
      e.target.style.left=`${coords.left+movX}px`;
      e.target.style.top=`${coords.top+movY}px`;
    }
  }

My collision detection function works, strictly speaking. I output the "Collision" event to a div so you can see this in the fiddle when you drag it around. BUT, you are still able to drag the divs on top of one another.

They kind of "stick" a little bit when you try to pull them apart, and if you keep pushing at them they'll overlap. The collision detection vacillates between true/false pretty rapidly at this point, so I'm guessing maybe some weirdness is going on here, but I can't figure it out.

I think one problem might be that the collision detection only outputs a collision when the borders are equal. That is, it returns false once a collision has happened and one element is inside another.

However, I can't see how my mousemove e.movementX and e.movementY events are able to get past the collision test and move the div.

Aram answered 8/1, 2017 at 23:8 Comment(3)
In your example, your 'items' are square. Will this always be the case?Eyebrow
They could be rectangular as well.Aram
The answer I gave below handles rectangles, as long as they are all the same size. If not you may need to fiddle a bit...Eyebrow
P
1

You will have the object colliding with more than just 1. The script will give you all the collisions. But the logic to accept/move it or no i guess depends on what it is you are trying to achieve. Borrowed from intersects

script:

function mouseMove(e) {
  if (!mouseDown) {
    return;
  }
  let coords = e.target.getBoundingClientRect();
  let movX = e.movementX;
  let movY = e.movementY;

  collision(movX, movY, e.target.classList[1], coords) //check all collisions. Item can collide with more than one polygon.

  e.target.style.left = `${coords.left+movX}px`;
  e.target.style.top = `${coords.top+movY}px`;
  /* if (!) {

  }*/
}

function collision(newX, newY, movingPart, movingRect) {
  let takenPositions = []; //array of arrays of rects' L, R, Top, Bottom coords
  let newCoords = {
    id: movingPart,
    width: 100,
    height: 100,
    x: movingRect.left + newX,
    y: movingRect.top + newY
  };

  let collision = false;
  let collisions = []; //store collisions. 
  divs.forEach((d) => {
    if (d.classList[1] !== movingPart) { // a thing can't collide with itself
      let c = d.getBoundingClientRect();
      takenPositions.push({
        id: d.classList[1],
        width: 100,
        height: 100,
         x: c.left,//updated this part x,y are undefined :|
         y: c.top //and updated this
      });
    }
  });

  takenPositions.forEach((p) => {
    var tw = p.width;
    var th = p.height;
    var rw = newCoords.width;
    var rh = newCoords.height;
    if (rw <= 0 || rh <= 0 || tw <= 0 || th <= 0) {
      collision = false;
    } else {
      var tx = p.x;
      var ty = p.y;
      var rx = newCoords.x;
      var ry = newCoords.y;
      rw += rx;
      rh += ry;
      tw += tx;
      th += ty;
      collision = ((rw < rx || rw > tx) && (rh < ry || rh > ty) && (tw < tx || tw > rx) && (th < ty || th > ry));
      collisions.push({
        parentId: newCoords.id,
        destId: p.id,
        collision: collision
      });
    }
  });
  let info = document.querySelector('div.info');
  info.innerHTML = "";
  collisions.forEach(function(element) {
    info.innerHTML += `${element.parentId} collision with ${element.destId} is ${element.collision}.  <br/>`;
  });
}
Prying answered 9/1, 2017 at 21:34 Comment(2)
Thank you for the link to intersects. I might be incredibly dense right now, but for some reason the code does not log any collisions among the elements: fiddleAram
@thomasdanner sorry, i updated the code. takenPosition x and y values supposed to be x: c.left, y:c.top. i just tried it in the fiddle. let mee know..Prying
E
1

This is a bit more complex than it looks.

Essentially what you need to do is get both sets of coordinates, those for the current (moving) element, and the one with which it has collided. Once a collision is detected, find out which axis has the smallest difference, then snap those coordinates.

var ac = a.getBoundingClientRect(); // coordinates for element 'a'
var bc = b.getBoundingClientRect(); // and 'b'

// assuming both boxes are same size...
// if not, use your existing collision code.

if(Math.abs(ac.top - bc.top) < ac.height && Math.abs(ac.left - bc.left) < ac.width) {
// collision here...

    if(Math.abs(ac.top - bc.top) < Math.abs(ac.left - bc.left)) {
    // vartical offset is smaller, so snap 'y's

        if(ac.top < bc.top) { // a is above b, so snap a's bottom to b's top
            a.style.top = bc.top - ac.height - 1 + 'px';
        }
        else {
            a.style.top = bc.top + bc.height + 1 + 'px';
        }

    }
    else { // here, horizontal offset is smaller, so snap 'x's

        if(ac.left < bc.left) { // a is to the left of b, so snap a's right to b's left
            a.style.left = bc.left - ac.width - 1 + 'px';
        }
        else {
            a.style.left = bc.left + bc.width + 1 + 'px';
        }

    }

}

That should solve your problem...

Eyebrow answered 9/1, 2017 at 8:55 Comment(0)
P
1

You will have the object colliding with more than just 1. The script will give you all the collisions. But the logic to accept/move it or no i guess depends on what it is you are trying to achieve. Borrowed from intersects

script:

function mouseMove(e) {
  if (!mouseDown) {
    return;
  }
  let coords = e.target.getBoundingClientRect();
  let movX = e.movementX;
  let movY = e.movementY;

  collision(movX, movY, e.target.classList[1], coords) //check all collisions. Item can collide with more than one polygon.

  e.target.style.left = `${coords.left+movX}px`;
  e.target.style.top = `${coords.top+movY}px`;
  /* if (!) {

  }*/
}

function collision(newX, newY, movingPart, movingRect) {
  let takenPositions = []; //array of arrays of rects' L, R, Top, Bottom coords
  let newCoords = {
    id: movingPart,
    width: 100,
    height: 100,
    x: movingRect.left + newX,
    y: movingRect.top + newY
  };

  let collision = false;
  let collisions = []; //store collisions. 
  divs.forEach((d) => {
    if (d.classList[1] !== movingPart) { // a thing can't collide with itself
      let c = d.getBoundingClientRect();
      takenPositions.push({
        id: d.classList[1],
        width: 100,
        height: 100,
         x: c.left,//updated this part x,y are undefined :|
         y: c.top //and updated this
      });
    }
  });

  takenPositions.forEach((p) => {
    var tw = p.width;
    var th = p.height;
    var rw = newCoords.width;
    var rh = newCoords.height;
    if (rw <= 0 || rh <= 0 || tw <= 0 || th <= 0) {
      collision = false;
    } else {
      var tx = p.x;
      var ty = p.y;
      var rx = newCoords.x;
      var ry = newCoords.y;
      rw += rx;
      rh += ry;
      tw += tx;
      th += ty;
      collision = ((rw < rx || rw > tx) && (rh < ry || rh > ty) && (tw < tx || tw > rx) && (th < ty || th > ry));
      collisions.push({
        parentId: newCoords.id,
        destId: p.id,
        collision: collision
      });
    }
  });
  let info = document.querySelector('div.info');
  info.innerHTML = "";
  collisions.forEach(function(element) {
    info.innerHTML += `${element.parentId} collision with ${element.destId} is ${element.collision}.  <br/>`;
  });
}
Prying answered 9/1, 2017 at 21:34 Comment(2)
Thank you for the link to intersects. I might be incredibly dense right now, but for some reason the code does not log any collisions among the elements: fiddleAram
@thomasdanner sorry, i updated the code. takenPosition x and y values supposed to be x: c.left, y:c.top. i just tried it in the fiddle. let mee know..Prying

© 2022 - 2024 — McMap. All rights reserved.