Search code examples
javascripthtmlcssdrag-and-dropelement

Drag'n'drop: Element not staying at its original position when I click on it


I am writing my own Drag'n'drop functionality for one of my projects and I am running into an issue. All my "draggable" elements are inside a container with display:flex. On mousedown event on one of this elements I set the position to absolute so I would be able to set the left and top properties of the element when I am dragging. Here is what I am doing:

let container = document.querySelector("#big-container")
var dragging = false;
var draggedObject;
let shiftX=0;
let shiftY=0;
document.querySelectorAll(".draggable").forEach((draggable,index) => {
    draggable.style.order = index;
    draggable.draggable =false;
    draggable.ondragstart = ()=>{return false}
    draggable.addEventListener("mousedown",ev =>{
        draggedObject = draggable;
        shiftX = ev.offsetX+5;
        shiftY = ev.offsetY+5;
        draggable.style.position = "absolute";
        draggable.style.left = (ev.clientX - shiftX) + 'px';
        draggable.style.top = (ev.clientY - shiftY) + 'px';
        dragging = true;
        let placeholder = document.createElement("div");
        placeholder.id = "placeholder";
        placeholder.style.order = draggable.style.order;
        container.appendChild(placeholder);
    })

})

document.addEventListener("mousemove", ev =>{
    if(dragging){
        draggedObject.style.left = ev.clientX - shiftX + 'px';
        draggedObject.style.top = ev.clientY - shiftY + 'px';
    }
})

document.addEventListener("mouseup",ev =>{
    if(dragging){
        draggedObject.style.position = 'static'
        let placeholder = document.querySelector("#placeholder");
        container.removeChild(placeholder);
        dragging = false
    }
})
/* the :not(:last-of-type(div)) is there so the console doesn't get affected */
*{
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    background-color: black;
}
.draggable {
    width: 90px;
    height: 120px;
    margin: 5px;
}


#placeholder {
    width: 90px;
    height: 120px;
    margin: 5px;
    background-color: rgba(0, 0, 0, 0.3);
    border: dashed grey 5px;
}
<body draggable="false" ondragstart="return false;">
<div id = "big-container" style ="display: flex; background-color: rgb(76, 104, 95); width: 500px; height: 500px;">
    <div style="background-color: rgb(204, 125, 111);" class="draggable"></div>
    <div style="background-color: rgb(170, 214, 120);" class="draggable"></div>
    <div style="background-color: rgb(129, 212, 167);" class="draggable"></div>
    <div style="background-color: rgb(162, 137, 196);" class="draggable"></div>
</div>
</body>

What I am trying to achieve is that on mousedown the element should stay where it was and after that when I move my mouse to move the element as well.(the anchor point should be where I clicked the element) I am doing shiftX = ev.offsetX+5; because I need to account for the element's margin.

The issue is when I click on a element(and don't move my mouse at all), you can see a little shift in the element's position. It is very minor(maybe 1 or 2px) and is not happening in all places(some zones in the element do not introduce this position shift)

Do you guys have any idea what might be causing it?


Solution

  • You can use getBoundingClientRect() to get the actual position.

    let container = document.querySelector("#big-container");
    var dragging = false;
    var draggedObject;
    let shiftX = 0;
    let shiftY = 0;
    document.querySelectorAll(".draggable").forEach((draggable, index) => {
      draggable.style.order = index;
      draggable.draggable = false;
      draggable.ondragstart = () => {
        return false;
      };
      draggable.addEventListener("mousedown", (ev) => {
        draggedObject = draggable;
        var x = draggable.getBoundingClientRect().top - 5;
        var y = draggable.getBoundingClientRect().left - 5;
        shiftX = ev.offsetX + 5;
        shiftY = ev.offsetY + 5;
        draggable.style.position = "absolute";
        draggable.style.left = y + "px";
        draggable.style.top = x + "px";
        dragging = true;
        let placeholder = document.createElement("div");
        placeholder.id = "placeholder";
        placeholder.style.order = draggable.style.order;
        container.appendChild(placeholder);
      });
    });
    
    document.addEventListener("mousemove", (ev) => {
      if (dragging) {
        draggedObject.style.left = ev.clientX - shiftX + "px";
        draggedObject.style.top = ev.clientY - shiftY + "px";
      }
    });
    
    document.addEventListener("mouseup", (ev) => {
      if (dragging) {
        draggedObject.style.position = "static";
        let placeholder = document.querySelector("#placeholder");
        container.removeChild(placeholder);
        dragging = false;
      }
    });
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
      background-color: black;
    }
    #big-container {
      width: 500px;
      height: 500px;
    }
    .draggable {
      width: 90px;
      height: 120px;
      margin: 5px;
    }
    
    #placeholder {
      width: 90px;
      height: 120px;
      margin: 5px;
      background-color: rgba(0, 0, 0, 0.3);
      border: dashed grey 5px;
    }
    <body draggable="false" ondragstart="return false;">
        <div
          id="big-container"
          style="display: flex; background-color: rgb(76, 104, 95);"
        >
          <div
            style="background-color: rgb(204, 125, 111);"
            class="draggable"
          ></div>
          <div
            style="background-color: rgb(170, 214, 120);"
            class="draggable"
          ></div>
          <div
            style="background-color: rgb(129, 212, 167);"
            class="draggable"
          ></div>
          <div
            style="background-color: rgb(162, 137, 196);"
            class="draggable"
          ></div>
        </div>
    
      </body>