본문 바로가기
javascript/e.g.

[e.g.] Drag and drop element in a list (+placeholder, clone item)

by by-choice 2021. 10. 23.
반응형

1. structure

<div id="list">
  <div class="draggable">A</div>
  <div class="draggable">B</div>
  <div class="draggable">C</div>
  <div class="draggable">D</div>
  <div class="draggable">E</div>
</div>

2. css

.opacity { /*  */
  opacity: 0.5;
}
.drag { /*  */
  position: fixed;
  box-shadow: 0 8px 12px rgba(0, 0, 0, 0.16);
}
.placeholder { /*  */
  margin-bottom: 10px;
  height: 4px;
  margin-top: -14px; /* default space + placeholder height */ 
  transform: translateY(7px); /* placeholder margin-top / 2 */ 
  background-color: deeppink;
  border-radius: 2px;
}

2. script

document.addEventListener('DOMContentLoaded', function () {
  const list = document.getElementById('list');
  let dragEle;
  let cloneEle;
  let placeholder;
  let isDraggingStarted = false;

  let x = 0;
  let y = 0;

  const swap = function (nodeA, nodeB) {
    const parentA = nodeA.parentNode;
    const siblingA = nodeA.nextSibling === nodeB ? nodeA : nodeA.nextSibling;
    nodeB.parentNode.insertBefore(nodeA, nodeB);
    parentA.insertBefore(nodeB, siblingA);
  };

  const isAbove = function (nodeA, nodeB) {
    const rectA = nodeA.getBoundingClientRect();
    const rectB = nodeB.getBoundingClientRect();
    return rectA.top + rectA.height / 2 < rectB.top + rectB.height / 2;
  };

  const mouseDownHandler = function (e) {
    if (e.button == 0) {
      document.addEventListener('mousemove', mouseMoveHandler);
      document.addEventListener('mouseup', mouseUpHandler);
    }
  };

  const mouseMoveHandler = function (e) {

    if (!isDraggingStarted) {
      dragEle = e.target.closest('.draggable'); 
      cloneEle = dragEle.cloneNode(true);
      cloneEle.classList.add('opacity'); 
      dragEle.classList.add('drag'); 
      const draggingRect = dragEle.getBoundingClientRect();

      isDraggingStarted = true;

      dragEle.insertAdjacentElement('afterend', cloneEle);
      const rect = dragEle.getBoundingClientRect();
      x = e.pageX - rect.left + window.scrollX;
      y = e.pageY - rect.top + window.scrollY;

      placeholder = document.createElement('div');
      placeholder.classList.add('placeholder');
      dragEle.parentNode.insertBefore(placeholder, dragEle.nextSibling);
    }

    dragEle.style.top = `${e.pageY - y}px`;
    dragEle.style.left = `${e.pageX - x}px`;

    const prevEle = dragEle.previousElementSibling;
    const nextEle = placeholder.nextElementSibling;

    if (prevEle && isAbove(dragEle, prevEle)) {
      swap(placeholder, dragEle);
      swap(placeholder, prevEle);
      return;
    }

    if (nextEle && isAbove(nextEle, dragEle)) {
      swap(nextEle, placeholder);
      swap(nextEle, dragEle);
    }
  };

  const mouseUpHandler = function () {
    document.removeEventListener('mousemove', mouseMoveHandler);
    document.removeEventListener('mouseup', mouseUpHandler);

    placeholder && placeholder.parentNode.removeChild(placeholder);
    cloneEle && cloneEle.parentNode.removeChild(cloneEle);

    dragEle.style.removeProperty('top');
    dragEle.style.removeProperty('left');
    dragEle.style.removeProperty('position');
    dragEle.classList.remove('drag'); 

    x = null;
    y = null;
    dragEle = null;
    cloneEle = null;
    isDraggingStarted = false;

  };

  [].slice.call(list.querySelectorAll('.draggable')).forEach(function (item) {
    item.addEventListener('mousedown', mouseDownHandler);
  });
});

 

반응형

댓글