javascriptjquerydrag-and-droponmouseup

How to capture mouseup event outside of mousedown element? (i.e. for proper Drag-n-Drop?)


The Javascript onmouseup event is not triggered if the mouse button is released outside the element on which onmousedown has been triggered.

This causes a drag&drop bug in JQuery UI: A JQuery draggable element does not stop dragging when mouse button is released outside of its container (because the element will stop moving when reaching it's parent boundaries). Steps to reproduce:

I see that behavior in latest Chrome and IE.

Is there any work-around?

I know that we could stop dragging the container on mouseout or mouseleave, but I would like to keep dragging, even if I am outside the parent container, much like in google maps (no matter, where you release the mouse, it always stops dragging the map).


Solution

  • You can have your mousedown element "capture" the pointer. Then it would always receive the mouseup event. In React this could look like this:

    const onPointerDownDiv1 = (event: React.PointerEvent) => {
      (event.target as HTMLDivElement).setPointerCapture(event.pointerId);
      // Do something ...
    };
    
    const onPointerUpDiv1 = (event: React.PointerEvent) => {
      (event.target as HTMLDivElement).releasePointerCapture(event.pointerId);
      // Do something ...
    };
    
    <div
      ref={div1}
      id="div1"
      className="absolute top-[200px] left-[390px] h-8 w-8 bg-red-300"
      onPointerDown={onPointerDownDiv1}
      onPointerUp={onPointerUpDiv1}
    />
    

    And here is an implementation using "plain vanilla" html + javascript:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
      </head>
      <body>
        <div
          id="div1"
          style="
            position: absolute;
            left: 50px;
            top: 50px;
            width: 20px;
            height: 20px;
            background-color: red;
          "
        ></div>
      </body>
      <script>
        let isDragging = false;
        let offsetX = 0;
        let offsetY = 0;
        let divElement = document.getElementById("div1");
    
        divElement.addEventListener("pointerdown", onPointerDown);
        divElement.addEventListener("pointermove", onPointerMove);
        divElement.addEventListener("pointerup", onPointerUp);
    
        function onPointerDown(event) {
          divElement.setPointerCapture(event.pointerId);
          offsetX = event.clientX - divElement.offsetLeft;
          offsetY = event.clientY - divElement.offsetTop;
          isDragging = true;
        }
    
        function onPointerMove(event) {
          if (isDragging) {
            divElement.style.left = (event.clientX - offsetX).toString() + "px";
            divElement.style.top = (event.clientY - offsetY).toString() + "px";
          }
        }
    
        function onPointerUp(event) {
          divElement.releasePointerCapture(event.pointerId);
          isDragging = false;
        }
      </script>
    </html>