Javascript drag and drop not working with pointer events

218 Views Asked by At

I am new to javascript and web development a whole, so I am sure there is a concept I am missing here and hopefully an easy way to fix this. I am using pointer events for drag and drop and it seems that when using touch it works fine. But when using a mouse, the draggable div will have unpredictable behavior. If you move the mouse quickly while dragging the div, the cursor will loose the element and the div will get stuck in limbo. What is also strange, and possibly related, is that the pointermove event will fire without first having pointerdown fire.

I thought some type of default behavior was causing this issue so I have added a pointercancel event (which didn't help). And I have added e.preventDefault() to the handlers but this didn't help either.

<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="styles.css">
    <script src="script.js" defer></script>
    <title>Document</title>
  </head>

  <body>
    <div>
      <div class="container" id="lineup">
        <div class=draggable id="player1" draggable="false">1</div>
        <div class=draggable id="player2" draggable="false">2</div>
        <div class=draggable id="player3" draggable="false">3</div>
      </div>
    </div>
  </body>

</html>
* {
box-sizing: border-box;
}

body {
    font-family: Arial, Helvetica, sans-serif;
    margin: 0;
}
#player{
    border: 2px solid white;
    border-radius: 6px;
    margin: 2px;

}
.dragging{
    border: 5px solid white;
    border-radius: 6px;
    margin: 2px;
    color:blue; 
   
}

.draggable.dragging {
    opacity: .5;
}

.draggable{
    height: 50px;
    width: 50px;
    border: 1px solid #000;
    border-radius: 10px;
    float: left;
    background: blueviolet;
    margin: 10px;
    touch-action: none;

}
const draggables = document.querySelectorAll('.draggable')

draggables.forEach(draggable => {

  draggable.addEventListener('pointerdown', e => {
    e.preventDefault()
    console.log(e)
    draggable.classList.add('dragging')
    draggable.setAttribute('draggable', true)
  })
  
  draggable.addEventListener('pointermove', e => {
    console.log(e)
    e.preventDefault()
    const player = document.getElementById(e.target.attributes.id.nodeValue)
    if (player == null) return
    if (player.classList.contains('dragging')) {
      positionPlayer(e, player)
    } else {
      return
    }
  })
  
  draggable.addEventListener('pointerup', e => {
    console.log(e)
    e.preventDefault()
    draggable.classList.remove('dragging')
    draggable.setAttribute('draggable', false)
  })
  draggable.addEventListener('pointercancel', e => {
    console.log(e)
    e.preventDefault()
  })
})

function positionPlayer(e, player) {
  e.preventDefault()
  player.style.position = 'absolute'
  player.style.left = `${e.pageX-player.clientWidth/2}px`
  player.style.top = `${e.pageY-player.clientWidth/2}px`
  console.log(e)

}

https://jsfiddle.net/MG_03035/fcjxb5Lp/

2

There are 2 best solutions below

0
On BEST ANSWER

Thanks Nick. I see you fixed the mouse issue by putting the listener on the document. The touch listeners did not work this way. I had to put them on the div to get them to work...kinda. I was looking for an easy way to use both types of listeners. To that end, the answer is jQuery. I have just started with web development so I had no idea jQuery existed. :) To anyone who may be reading this, that is new to web development like me, I suggest you learn about vanilla js and then jQuery. You will appreciate how helpful and efficient jQuery is.

0
On

I would suggest to use position: absolute; so that you can easily then rely on the cursors position to apply placement for the drag and drop functionality.

I would also then suggest using mouseup, mousedown and mousemove for the event listeners to better capture the kinds of user responses you're looking to work with.

Finally, I would layout the div's with margin before the drag functionality has been triggered, and then remove the margin on the div's once selected.

let offsetX, offsetY;
let isDragging = false;
let currentDraggable = null;

document.addEventListener('mousedown', (e) => {
  if (e.target.classList.contains('draggable')) {
    isDragging = true;
    currentDraggable = e.target;
    offsetX = e.clientX - currentDraggable.getBoundingClientRect().left;
    offsetY = e.clientY - currentDraggable.getBoundingClientRect().top;
    currentDraggable.style.margin = 0;
    currentDraggable.classList.add('dragging');
    currentDraggable.style.cursor = 'grabbing';
  }
});

document.addEventListener('mousemove', (e) => {
  if (!isDragging) return;

  const x = e.clientX - offsetX;
  const y = e.clientY - offsetY;

  currentDraggable.style.left = `${x}px`;
  currentDraggable.style.top = `${y}px`;
});

document.addEventListener('mouseup', () => {
  if (isDragging) {
    isDragging = false;
    currentDraggable.classList.remove('dragging');
    currentDraggable.style.cursor = 'grab';
    currentDraggable = null;
  }
});
body {
  margin: 0;
  padding: 10px;
  overflow: hidden;
  font-family: Arial, Helvetica, sans-serif;
}

div:nth-child(2) {
  margin-left: 70px;
}

div:nth-child(3) {
  margin-left: 135px;
}

.draggable {
  width: 50px;
  height: 50px;
  border: 1px solid #000;
  border-radius: 10px;
  background-color: blueviolet;
  position: absolute;
  cursor: grab;
  margin: 5px;
}

.dragging {
  border: 5px solid white;
  margin: 2px;
  color: blue;
}

.draggable.dragging {
  opacity: .5;
}
<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="styles.css">
    <script src="script.js" defer></script>
    <title>Document</title>
  </head>

  <body>
    <div>
      <div class="container" id="lineup">
        <div class=draggable id="player1">1</div>
        <div class=draggable id="player2">2</div>
        <div class=draggable id="player3">3</div>
      </div>
    </div>
  </body>

</html>