最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

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

programmeradmin2浏览0评论

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:

  • Go to /.
  • Drag the draggable downward until the mouse has left the surrounding container
  • Release mouse button (no mouse button is pressed at this point)
  • Move mouse back into container
  • And the draggable is still being dragged. I would expect the dragging to have stopped as soon as I released the mouse button - no matter where it is released.

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).

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:

  • Go to http://jqueryui.com/draggable/.
  • Drag the draggable downward until the mouse has left the surrounding container
  • Release mouse button (no mouse button is pressed at this point)
  • Move mouse back into container
  • And the draggable is still being dragged. I would expect the dragging to have stopped as soon as I released the mouse button - no matter where it is released.

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).

Share Improve this question edited Sep 18, 2021 at 6:15 Domi asked Jan 12, 2014 at 11:02 DomiDomi 24.5k19 gold badges102 silver badges132 bronze badges 2
  • containment:'parent' ??? jsfiddle.net/4wX58 – A. Wolff Commented Jan 12, 2014 at 11:12
  • 1 PS: I see that when mouse button is released outside of document, element is still draggable :( – A. Wolff Commented Jan 12, 2014 at 11:17
Add a comment  | 

4 Answers 4

Reset to default 6

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>

I found this to be the best solution: Attach the mouseup event handler to document instead. Then it will always cancel, even if you release the mouse button outside the browser. Of course, this is not a pretty solution, but apparently, this is the only way to get dragging to work correctly.

Try the solution below:

  • You will see that "Drag END" will always happen, no matter where you release the cursor.
  • Also, in order to prevent text selection while dragging, I added an unselectable class.

let dragging = false;
const dragEl = document.querySelector('div');
const logEl = document.querySelector('pre');

dragEl.addEventListener('mousedown touchstart', (evt) => {
  dragging = true;
  dragEl.classList.add('unselectable');
  logEl.textContent += 'drag START\n';
});
document.addEventListener('mouseup touchend', (evt) => {
  if (dragging) {
    event.preventDefault();
    dragEl.classList.remove('unselectable');
    dragging = false;
    logEl.textContent += 'drag END\n';
  }
});
div {
  background: red;
}

.unselectable {
  -webkit-user-select: none; /* Safari */        
  -moz-user-select: none; /* Firefox */
  -ms-user-select: none; /* IE10+/Edge */
  user-select: none; /* Standard */
}
<div>drag me</div>
<hr>
LOG:
<p><pre></pre></p>

Update

These days, the setPointerCapture API provides a cleaner solution, as explained in this answer.

The best way to handle this is to check the current button status in the mousemove event handler. If the button is no longer down, stop dragging.

If you put the mouseup event on the document then you just move the problem up, the original problem will still show itself if you release the button outside of the document (or even outside of the browser window).

For example with jQuery

$div.on("mousedown", function (evt) {
        dragging = true;
  }).on("mouseup", function (evt) {
        dragging = false;
  }).on("mousemove", function (evt) {
        if (dragging && evt.button == 0) {
            //Catch case where button released outside of div
            dragging = false;
        }
        if (dragging) {
          //handle dragging here
        }
  });

You could skip the mouseup event handler because it will be caught by the mousemove but I think it's more readable with it still there.

If you create an "index.html" file with the following code in it (you can just copy and paste it entirely) it will show the working solution using just 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) {
        let newPosLeft = event.clientX - offsetX;
        if (newPosLeft < 30) {
          newPosLeft = 30;
        }
        let newPosTop = event.clientY - offsetY;
        if (newPosTop < 30) {
          newPosTop = 30;
        }
        divElement.style.left = newPosLeft.toString() + "px";
        divElement.style.top = newPosTop.toString() + "px";
      }
    }

    function onPointerUp(event) {
      divElement.releasePointerCapture(event.pointerId);
      isDragging = false;
    }
  </script>
</html>
发布评论

评论列表(0)

  1. 暂无评论