Drag & Drop 구현하기 [JavaScript]

SnowCat·2023년 9월 21일
0
post-thumbnail
  • React-Beautiful-Dnd를 사용하려 했더니 콘솔에 다음 버전에서 지원이 중단될 api가 사용되고 있다는 경고가 나왔다.
  • 라이브러리 의존도 줄일 겸 연습을 위해 직접 drag & drop 기능을 구현해보자.

Template

  • 아래와 같은 html에서 li에 drag & drop 기능을 구현하고 싶다.
<!--
... 
-->
<ul class="list-group">
  <option class="list-item" value=1>Option 1</option>
  <option class="list-item" value=2>Option 2</option>
  <option class="list-item" value=3>Option 3</option>
</ul>
<!--
... 
-->
  • 구현을 위해서는 li에 draggable=true 속성을 부여해야 한다.
<!--
... 
-->
<ul class="list-group">
  <option class="list-item" draggable=true value=0>Option 1</option>
  <option class="list-item" draggable=true value=1>Option 2</option>
  <option class="list-item" draggable=true value=2>Option 3</option>
</ul>
<!--
... 
-->
  • 이제 드래깅이 된다. Drop 기능을 구현하기 위해서는 Drag api를 사용해야 한다.

Drag API

  • Drag API를 통해 브라우저에서 어플리케이션이 드래그 앤 드롭 기능을 사용하게 해준다.
  • 유저는 draggable 속성이 부여된 요소를 마우스로 선택해 드래그하고, 마우스 버튼에서 손을 때면 element는 drop된다.
  • 드래그하는 동안 draggable 요소는 반투명하게 마우스 포인터를 따라다닌다.
  • 구체적인 이벤트는 아래와 같다.
    • drag: element, 텍스트 블록을 드래그 할 때 발생
    • dragend: 드래그가 끝났을 때 발생
    • dragenter: 드래그 하는 타겟이 드롭될 수 있는 element 위에 올라갔을 때 발생
    • dragexit: 요소가 직접적인 드래그 대상에서 벗어났을 때 발생
    • dragleave: 드래그 하는 타겟이 드롭될 수 있는 element 에서 벗어났을 때 발생
    • dragover: 드래그 중인 타겟이 드롭될 수 있는 element를 지나갈 때 발생
    • dragstart: 드래그를 시작했을 때 발생
    • drop: 마우스를 때고 element가 드롭될 때 발생
  • drag & drop 구현을 위해서는 dragstart, dragenter, dragend, dragover를 사용하면 된다.

자바스크립트 코드 구현하기

  • dragstart를 통해서 드래그 이벤트를 감지한다.
  • 이 때 현재 드래그 중인 node 객체와 인덱스를 저장해준다.
const list = document.querySelector(".list-group").children;

// Node, 인덱스를 저장해줄 변수
let draggingNode = null;
let draggingIndex = null;

// 드래그를 시작할 때 드래그 중인 element의 정보를 저장해주는 Event 등록
for (let node of list) {
  node.addEventListener("dragstart", (event) => {
    const {
      target: { value }
    } = event;
    for (let i = 0; i < list.length; i++) {
      const node = list[i];
      if (node.value === value) {
        draggingNode = node;
        draggingIndex = i;
        break;
      }
    }
  });
}
  • 그 다음으로 dragenter시마다 대상 타겟이 되는 list와 드래그 중인 list element를 스왑시켜주어야 한다.
  • dragstart에서 했던것과 동일한 방법으로 targetElement의 정보를 가져오고, node.before, node.after 메서드를 사용해 노드를 스왑해준다.
  node.addEventListener("dragenter", (event) => {
    // 스왑할 노드 정보 가져오기
    const {
      target: { value }
    } = event;
    const { target: targetNode } = event;
    let targetIndex = null;
    
    for (let i = 0; i < list.length; i++) {
      const node = list[i];
      if (node.value === value) {
        targetIndex = i;
        break;
      }
    }

    // 노드 스왑
    if (draggingIndex !== null && targetIndex !== null) {
      // 자신이 제일 위에 있게 될 때에는 before 메서드 사용
      targetIndex === 0
        ? targetNode.before(draggingNode)
        : targetNode.after(draggingNode);
    }
  });
  • dragend에서는 드래그가 되는 node를 초기화해준다.
  node.addEventListener("dragend", () => {
    draggingNode = null;
    draggingIndex = null;
  });
  • dragover에서는 이벤트 버블링을 방지해주기 위해 preventdefault 메서드를 사용해준다.
  node.addEventListener("dragover", (event) => {
    event.preventDefault();
  });

최종 코드 및 데모

데모 링크

HTML

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
  </head>
  <body>
    <ul class="list-group">
      <option class="list-item" draggable="true" value="1">Option 1</option>
      <option class="list-item" draggable="true" value="2">Option 2</option>
      <option class="list-item" draggable="true" value="3">Option 3</option>
    </ul>
    <script src="index.js"></script>
  </body>
</html>

JS

import "./styles.css";

const list = document.querySelector(".list-group").children;

let draggingNode = null;
let draggingIndex = null;

for (let node of list) {
  node.addEventListener("dragstart", (event) => {
    const {
      target: { value }
    } = event;
    for (let i = 0; i < list.length; i++) {
      const node = list[i];
      if (node.value === value) {
        draggingNode = node;
        draggingIndex = i;
        break;
      }
    }
  });

  node.addEventListener("dragenter", (event) => {
    const {
      target: { value }
    } = event;
    const { target: targetNode } = event;

    let targetIndex = null;
    for (let i = 0; i < list.length; i++) {
      const node = list[i];
      if (node.value === value) {
        targetIndex = i;
        break;
      }
    }

    if (draggingIndex !== null && targetIndex !== null) {
      targetIndex === 0
        ? targetNode.before(draggingNode)
        : targetNode.after(draggingNode);
    }
  });

  node.addEventListener("dragend", () => {
    draggingNode = null;
    draggingIndex = null;
  });

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

출처:
https://ko.javascript.info/bubbling-and-capturing
https://developer.mozilla.org/ko/docs/Web/API/HTML_Drag_and_Drop_API

profile
냐아아아아아아아아앙

0개의 댓글

관련 채용 정보