javascripthtmldragwebapi

Change the appearance of dragged element


my task is to create drag and drop interface, where user drags an element, which changes its appearance during that short amount of time - till it is dropped in its destination.

I would like to do it with native Web API. I have found the event called "dragstart" in MDN Documentation. And prepared this fiddle to demonstrate the behaviour.

const source = document.getElementById("draggable");
source.addEventListener("dragstart", (event) => {
  event.target.classList.add("dragging");
});

source.addEventListener("dragend", (event) => {
  event.target.classList.remove("dragging");
});
body {
  /* Prevent the user from selecting text in the example */
  user-select: none;
}

#container {
  width: 400px;
  height: 20px;
  background: blueviolet;
  padding: 10px;
}

#draggable {
  text-align: center;
  background: white;
}

.dragging {
  width: 422px;
  color: red;
  font-weight: 700;
}
<div id="container">
  <div id="draggable" draggable="true">Drag me</div>
</div>
<div class="dropzone"></div>

However the result is not sufficient. In my task I need the default element to persist its appearance and change only the dragged minimised version of it - ideally it should be narrower and show a little different content than the default element.

If you knew any source, where this task is resolved (anyhow), I'd be glad for your reply.


Solution

  • You can create your own ghost element by cloning the draggable element, apply your custom styles on it, append it to the document but move it outside the screen not to see duplicate elements, use setDragImage, and finally remove the ghost element from the document.

    to display the image so that the pointer is at its center, use values that are half the width and height of the image.

    so you can use

    event.dataTransfer.setDragImage(ghostEl, ghostEl.offsetWidth / 2, ghostEl.offsetHeight / 2);
    

    And for moving the ghost element outside the screen you can use absolute position or transform properties:

    transform: translateX(-100%);
    

    Final code:

    const source = document.getElementById("draggable");
    let ghostEl;
    
    source.addEventListener("dragstart", (event) => {
      ghostEl = event.target.cloneNode(true);
      ghostEl.classList.add("ghost");
      document.body.appendChild(ghostEl);
      event.dataTransfer.setDragImage(ghostEl, ghostEl.offsetWidth / 2, ghostEl.offsetHeight / 2);
    });
    
    source.addEventListener("dragend", (event) => {
      // reset the transparency
      document.body.removeChild(ghostEl);
    });
    body {
      /* Prevent the user from selecting text in the example */
      user-select: none;
    }
    
    #container {
      width: 400px;
      height: 20px;
      background: blueviolet;
      padding: 10px;
    }
    
    #draggable {
      text-align: center;
      background: white;
    }
    
    .ghost {
      width: 422px;
      color: red;
      font-weight: 700;
      transform: translateX(-100%);
    }
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8" />
      <link rel="icon" type="image/svg+xml" href="/vite.svg" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>App</title>
    </head>
    
    <body>
      <div id="container">
        <div id="draggable" draggable="true">Drag me</div>
      </div>
      <div class="dropzone"></div>
    </body>
    
    </html>