'How to move an HTML elment with the mouse inside parent but drag/drop outside of parent?

I'm fiddling with drag&drop in HTML and Javascript, setting the draggable attribute of elements and implementing dragstart, dragover and drop events to be able to drag and drop elements into a "drop field" and to drag & drop them out again.

That part works for me.

I now want to be able to move those elements using a similar gesture: press the mouse button over the element I want to move, move the mouse and release the button again, without having to press some modifier like CTRL.

Such a behavior can be implemented by handling mousedown/mousemove and mouseup events as described here.

But what if I want to combine them? To me it looks like dragging an element out of a field when moving it should also be possible, somehow get into each others way. However the workflow still seems valid: just register both events, pretend you just want to move until you leave the parent and then decide to either handle the drop event and return the element to it's original position or have it moved.

My first naive approach would be to just implement both (drag and drop and mouse-move) and somehow make sure, positions and event handling don't interfere.

Another approach would be to forget about the mouse events and stick to drag&drop instead which had to be configured to provide seamless moving.

Since I expect my resulting code to become quite dirty I was hoping for some more sophisticated approach to exist for a hybrid drag&drop and move behavior.

Can you give me a hint? how would you do this?

Here is some current state which allows creating a new element via drag&drop and move it around. As you can see I had to deactivate draggable for the mouse-events to work.

enter image description here

<!DOCTYPE html>
<html>
  <head><style>
    body, html, div, figure {
      margin: 0; padding: 0;
      background-color: grey;
      position: absolute;
      text-align: center;
    }
    .fullsize {
      background-color: rgb(200, 250, 250);
      width: 15cm; height: 15cm;
    }
    .dragZone {
      background-color: rgb(200, 250, 200);
      width: 3cm; height: 3cm;
      border-style: solid;
      border-width: 2px;
    }
    #source {
      background-color: rgb(200, 200, 250);
      left: 17cm; top: 2cm;
    }
  </style></head>
    <body>
      <div class="dragZone" id="source" draggable=true>drag me</div>
      <div class="fullsize" id="target_area">target</div>
    </body>
 
    <script>
      (function() {
        const target_area = document.getElementById("target_area");
        target_area.addEventListener("drop", (event) => {
          const relpos = JSON.parse(event.dataTransfer.getData("relpos") || "null");
          if (!relpos) return;
          
          const new_element = document.createElement("div");
          new_element.setAttribute("class", "dragZone");
          new_element.draggable = true;
          new_element.style.left = `${event.offsetX - relpos[0]}px`;
          new_element.style.top = `${event.offsetY - relpos[1]}px`;
          new_element.innerHTML = "drag&drop or move me";

          var isDown = false;

          new_element.addEventListener('mousedown', (e) => {
            console.log(`mouse down ${e}`);
            isDown = true;
            e.srcElement.draggable=false;
          }, true);

          new_element.addEventListener('mouseup', (e) => {
            console.log(`mouse up ${e}`);
            isDown = false;
            e.srcElement.draggable=true;
          }, true);

          new_element.addEventListener('mousemove', (e) => {
            e.preventDefault();
            if (!isDown) return;
            const elem = e.srcElement;
            const rect = elem.getBoundingClientRect();
            elem.style.left = `${rect.x + e.movementX}px`;
            elem.style.top  = `${rect.y + e.movementY}px`;
          }, true);
          
          target_area.appendChild(new_element);
        });

        target_area.addEventListener("dragover", (event) => {
          event.preventDefault();
        });

        document.getElementById("source").addEventListener("dragstart", (event) => {
          event.stopPropagation();
          event.dataTransfer.setData("relpos", JSON.stringify([event.offsetX, event.offsetY]));
        });
      })();
    </script>
</html>



Solution 1:[1]

Found it - instead implementing your own movement based on mouse events and fiddling with the drag/drop events you can just use the drag&drop mechanism for both.

To make it work you have to deactivate pointer-events for the dragged item to avoid unwanted dragenter/dragleave events for the parent and turn it back on again afterwards (it has to be activated by default to enable dragging in the first place).


draggable_element.addEventListener("dragstart", (e) => {
  e.srcElement.style.pointerEvents = "none";
  ... // rest of code
});

elem.addEventListener("dragend", (e) => {
  e.srcElement.style.pointerEvents = "auto";
  ... // rest of code
});

Here is a working example: https://jsfiddle.net/03a9s4ur/10/

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 frans