Search code examples
javascripthtmlcssdrag-and-drop

Javascript (vanilla) drag and drop does not work the second time an element is dragged and dropped


I am trying to implement a drag and drop functionality using vanilla Javascript on my web app, where element gets moved to a new position within a div, once it's dragged and dropped.

But I am having an issue where I can drop the element fine the first time, but I can no longer do it the second time onwards.

After debugging, I have noticed that the first time I drop an element, it does not have any inline style (see Appendix A). But when I try and do it the second time, it now has an inline style (see Appendix B) and for some reason, I cannot chnage the values of it. That was also the case after I manually added inline style to my draggable element- I could not drop the item even the first time when I did it.

I am completely out of ideas as to what I could be doing wrong and no similar questions yielded a solution.

Thank you very much in advance for your time.

Code (Unnecessary parts ommitted)

const list = document.querySelector("#list");
const rect = list.getBoundingClientRect();
let oldLeft, oldTop, mouseXStart, mouseYStart;

function dragStart(event) {
  event.dataTransfer.setData("plain/text", event.target.id);
  const item = document.querySelector("#" + event.target.id);

  mouseXStart = event.clientX - rect.left;
  mouseYStart = event.clientY - rect.top;
  oldLeft = item.style.left;
  oldTop = item.style.top;

  console.log(item);
}

function dragOver(event) {
  event.preventDefault();
  event.dataTransfer.dropEffect = "move";
}

function dropItem(event) {
  event.preventDefault();
  const mouseXEnd = event.clientX - rect.left;
  const mouseYEnd = event.clientY - rect.top;
  //Calculate by how much mouse has been moved
  const newLeft = mouseXEnd - mouseXStart;
  const newTop = mouseYEnd - mouseYStart;
  const item = document.querySelector('#' + event.dataTransfer.getData("plain/text"));

  item.style.left = oldLeft + newLeft + "px";
  item.style.top = oldTop + newTop + "px";
}
#list {
  position: relative;
  top: 60px;
  height: 600px;
  width: 100%;
  border: 2px solid rgb(107, 14, 14);
  display: block;
}

#list>div {
  position: absolute;
  height: 200px;
  width: 200px;
  background-color: blue;
  margin: 10px;
  overflow: hidden;
  text-align: left;
}
<div id="list" ondrop="dropItem(event)" ondragover="dragOver(event)">
  <div id="test" draggable="true" ondragstart="dragStart(event)">
    <button type="button"></button>
    <div>
      <p>Test</p>
    </div>
  </div>
</div>

Appendix A

console.log() output on the first dragStart() call:

<div id="test" draggable="true" ondragstart="dragStart(event)">

Appendix B

console.log() output on the second dragStart() call:

<div id="test" draggable="true" ondragstart="dragStart(event)" style="left: 853px; top: 147px;">

Solution

  • Problem

    This code

    oldLeft + newLeft + "px"
    

    evaluated to something like

    123px40px
    

    because

    oldLeft = item.style.left
    

    returned string with px at the end

    Solution

    Parse the value to float

    oldLeft = item.style.left ? parseFloat(item.style.left) : 0;
    oldTop = item.style.top ? parseFloat(item.style.top) : 0;
    

    const list = document.querySelector("#list");
    const rect = list.getBoundingClientRect();
    let oldLeft, oldTop, mouseXStart, mouseYStart;
    
    function dragStart(event) {
      event.dataTransfer.setData("plain/text", event.target.id);
      const item = document.querySelector("#" + event.target.id);
    
      mouseXStart = event.clientX - rect.left;
      mouseYStart = event.clientY - rect.top;
      oldLeft = item.style.left ? parseFloat(item.style.left) : 0;
      oldTop = item.style.top ? parseFloat(item.style.top) : 0;
    }
    
    function dragOver(event) {
      event.preventDefault();
      event.dataTransfer.dropEffect = "move";
    }
    
    function dropItem(event) {
      event.preventDefault();
      const mouseXEnd = event.clientX - rect.left;
      const mouseYEnd = event.clientY - rect.top;
      //Calculate by how much mouse has been moved
      const newLeft = mouseXEnd - mouseXStart;
      const newTop = mouseYEnd - mouseYStart;
      const item = document.querySelector('#' + event.dataTransfer.getData("plain/text"));
    
      item.style.left = oldLeft + newLeft + "px";
      item.style.top = oldTop + newTop + "px";
    }
    #list {
      position: relative;
      top: 60px;
      height: 600px;
      width: 100%;
      border: 2px solid rgb(107, 14, 14);
      display: block;
    }
    
    #list>div {
      position: absolute;
      height: 200px;
      width: 200px;
      background-color: blue;
      margin: 10px;
      overflow: hidden;
      text-align: left;
    }
    <div id="list" ondrop="dropItem(event)" ondragover="dragOver(event)">
      <div id="test" draggable="true" ondragstart="dragStart(event)">
        <button type="button"></button>
        <div>
          <p>Test</p>
        </div>
      </div>
    </div>