Search code examples
javascriptdrag-and-dropmouseeventaddeventlistenerdraggable

Mouse event listeners not working after 'dragstart' is fired in Javascript


I am currently developing a drag and drop interface and would like to implement a feature that allows users to return a dragged object to its original position by right-clicking.

In the demo below, the blue ball can be dragged to any of the 3 boxes. I added an event listener for a right-click mouseup event.

Desired outcome:

Anytime when I am dragging the ball, a right mouse click will end the drag event and return the ball to the original square it was dragged from.

Actual outcome:

When I am dragging the ball, a right mouse click does nothing. Only after I drop the ball does a right-click return the ball to the original square.

Potential problem identified

After testing, this issue may be due to the fact that additional mouse event listeners are not active during the 'dragging' phase. What should I do?

Code

const boxes = document.getElementsByClassName("box");
const ball = document.getElementById("ball");

for (var i = 0; i < boxes.length; i++) {
  boxes[i].addEventListener("dragover", function(event) {
    event.preventDefault();
  });
  boxes[i].addEventListener("drop", function(event) {
    event.preventDefault();
    this.appendChild(ball);
  });
}

var originalBox;
ball.setAttribute("draggable", true);
ball.addEventListener("dragstart", function(event) {
  originalBox = ball.closest(".box");
});

document.addEventListener('mouseup', (event) => {
  // (event.button === 2) checks if the right mouse button was clicked
  if (event.button === 2) {
    originalBox.appendChild(ball);
  }
});
.box {
  width: 100px;
  height: 100px;
  border: 1px solid black;
}

#ball {
  width: 90px;
  height: 90px;
  border-radius: 100%;
  background-color: DodgerBlue;
}
<div class="box">
  <div id="ball"></div>
</div>
<div class="box">
</div>
<div class="box">
</div>


Solution

  • I managed to write a workaround by following the guide here: Drag'n'Drop with mouse events

    Instead of using ball.setAttribute("draggable", true);, I am using ball.onmousedown = function(event).

    The movement of the ball is then tracked using

        moveAt(event.pageX, event.pageY);
    
        function moveAt(pageX, pageY) {
                ball.style.left = pageX - shiftX + 'px';
                ball.style.top = pageY - shiftY + 'px';
        }
    

    Hence, the drag event is simulated, and I do not have to use the native drag and drop functions. This way, listeners for onMouseUp can be added.

    Finally, to make right-click return the ball to the original position, I have added the following function onMouseUp(event) to simulate the "drop" event:

     if (event.button === 0) {
                        currentDroppable.append(ball);
                    } else {
                        originalSquare.append(ball);
                    }
    

    event.button === 0 refers to the left mouse button. The code above means that when the left mouse button is the one that is released, the ball is appended to the mouse's position. If not (i.e. if the right button or middle button is clicked), the ball is appended to the original position.

    I have adjusted the code snippet.

    Code

    let ball = document.getElementById('ball')
    let currentDroppable = null;
    let originalSquare = null;
    
    document.body.addEventListener("contextmenu", e => e.preventDefault());
    document.body.ondragstart = function() {
        return false;
    };
    
    ball.onmousedown = function(event) {
        if (event.button === 0) {
            originalSquare = this.parentElement;
            let shiftX = event.clientX - ball.getBoundingClientRect().left;
            let shiftY = event.clientY - ball.getBoundingClientRect().top;
        
            ball.style.position = 'absolute';
        
            moveAt(event.pageX, event.pageY);
        
            function moveAt(pageX, pageY) {
                ball.style.left = pageX - shiftX + 'px';
                ball.style.top = pageY - shiftY + 'px';
            }
        
            function onMouseMove(event) {   
                moveAt(event.pageX, event.pageY);
        
                ball.hidden = true;
                let elemBelow = document.elementFromPoint(event.clientX, event.clientY);
                ball.hidden = false;
        
                if (!elemBelow) return;
        
                let droppableBelow = elemBelow.closest('.box');
                if (currentDroppable != droppableBelow) {
                    currentDroppable = droppableBelow;
                }
            }
        
      
            function onMouseUp(event) {
                document.removeEventListener('mousemove', onMouseMove);
                    if (currentDroppable) {
                        if (event.button === 0) {
                            currentDroppable.append(ball);
                        } else {
                            originalSquare.append(ball);
                        }
                    } else {
                        originalSquare.append(ball);
                    };
                document.removeEventListener('mouseup', onMouseUp);
                ball.style.position = null;
            }
    
            document.addEventListener('mousemove', onMouseMove);
            document.addEventListener('mouseup', onMouseUp);
        }
    }
    
    ball.ondragstart = function() {
        return false;
    };
    .box {
      width: 100px;
      height: 100px;
      border: 1px solid black;
    }
    
    #ball {
      width: 90px;
      height: 90px;
      cursor: default;
      border-radius: 100%;
      background-color: DodgerBlue;
      z-index: 2;
    }
    <div class="box">
      <div id="ball"></div>
    </div>
    <div class="box">
    </div>
    <div class="box">
    </div>