HTML Drag and Drop api 를 이용하여 드래그 앤 드롭 트리 구현하기

혜삐·2024년 6월 11일
0

Drag and Drop이 가능한 트리를 구현해달라는 요구사항을 받고, 관련 라이브러리를 찾다가 문제를 발견했다.

  1. vue3 와 호환이 되어야한다.
  2. 기존 데이터 구조를 많이 바꾸지 않아야한다.
  3. 부모와 자식은 바뀌면 안 되는 등 여러 제약 조건이 있어서 커스텀이 잘 되어야한다.

그렇지만 위의 사항들을 만족하는 vue3 라이브러리가 없어서 직접 구현하기로 결정..!

요구사항

  • [] drag and drop이 되어야한다.
  • [] 이 때 사용자가 알 수 있게 선택한 항목에 대해 css가 변경되어야한다.
  • [] 데이터가 바뀌었다면 이 때마다 백엔드에 api로 요청해야한다.

구현 전 데이터 구조 확인

  1. 데이터
const data = [
  {
    seq: 1,
    title: "사과 🍎",
  },
  {
    seq: 2,
    title: "배 🍐",
  },
  {
    seq: 3,
    title: "포도 🍇",
  },
];
  1. 화면
<template>
  <div class="item">
    <ul v-bind:key="item.seq" v-for="item in data">
      <li class="draggable" draggable="true">
        {{ item.title }}
      </li>
    </ul>
  </div>
</template>

실행 화면은 다음과 같다

Drag and Drop 구현하기

Drag and Drop 각 이벤트 단계에 맞는 액션을 연결해주면 되기에 우선 각 단계에 맞는 함수들을 정의했다.

1. 액션별 기능 정의


// 1. 마운트되면 각각 아이템들에 드래그앤드롭 감지 함수들을 연결
onMounted(() => {
  var listItems = document.querySelectorAll(".draggable");
  listItems.forEach(function (item) {
    addEventsDragAndDrop(item);
  });
});

function addEventsDragAndDrop(el) {
  el.addEventListener("dragstart", dragStart);
  el.addEventListener("dragover", dragOver);
  el.addEventListener("dragleave", dragLeave);
  el.addEventListener("drop", dragDrop);
  el.addEventListener("dragend", dragEnd);
}

function dragStart(e) {
  // css 추가
  // 드래그 하는 대상의 데이터 값 감지 필요
}

function dragLeave(e) {
  // css 해제
}

function dragOver(e) {
  // css 추가
}

function dragDrop(e) {
  // 선택한 아이템과 놓은 자리의 아이템이 바뀌어야함
}

function dragEnd(e) {
  // css 해제
}

2. CSS 설정

유저입장에서 편할 수 있도록 선택한 아이템이 드래그될 때는 투명도를 낮춰서 선택되어있음을 인지할 수 있게 하였고, 드래그로 아이템을 가져다 놓으려는 곳의 크기를 살짝 키워줬다. 단, 위아래로만 키워서 화면 변화가 너무 크지 않도록 하였다.


// 1. 마운트되면 각각 아이템들에 드래그앤드롭 감지 함수들을 연결
onMounted(() => {
  var listItems = document.querySelectorAll(".draggable");
  listItems.forEach(function (item) {
    addEventsDragAndDrop(item);
  });
});

function addEventsDragAndDrop(el) {
  el.addEventListener("dragstart", dragStart);
  el.addEventListener("dragover", dragOver);
  el.addEventListener("dragleave", dragLeave);
  el.addEventListener("drop", dragDrop);
  el.addEventListener("dragend", dragEnd);
}

function dragStart(e) {
  // css 추가
  // 드래그 하는 대상의 데이터 값 감지 필요
  this.style.opacity = "0.4";
}

function dragLeave(e) {
  // css 해제
  this.classList.remove("over");
}

function dragOver(e) {
  // css 추가
  this.classList.add("over");
}

function dragDrop(e) {
  // 선택한 아이템과 놓은 자리의 아이템이 바뀌어야함
  this.classList.remove("over");
}

function dragEnd(e) {
  // css 해제
  this.style.opacity = "1";
  this.classList.remove("over");
}
<style>
.over {
  transform: scale(1, 1.1);
}
</style>

  1. 기능 구현

우선 드래그했을 때 대상을 저장하는 변수를 하나 둔다

let dragSrcEl = null;

그 다음 드래그 시작시에는 그 값을 지정해주고, 드래그 드롭시에 값을 바꿔준다.

어떤 태그가 위에 머무를 때 작동하는 이벤트인데, Over 이벤트의 default 속성이 다음과 같다.
Reset the current drag operation to "none". 즉, drag 기능이 꺼지므로 preventDefault()를 이용해서 기본 기능을 비활성화시켜줘야한다.

<script lang="ts" setup>
import { onMounted } from "vue";

onMounted(() => {
  var listItems = document.querySelectorAll(".draggable");
  listItems.forEach(function (item) {
    addEventsDragAndDrop(item);
  });
});

let dragSrcEl = null;

function addEventsDragAndDrop(el) {
  el.addEventListener("dragstart", dragStart);
  el.addEventListener("dragover", dragOver);
  el.addEventListener("dragleave", dragLeave);
  el.addEventListener("drop", dragDrop);
  el.addEventListener("dragend", dragEnd);
}

function dragStart(e) {
  this.style.opacity = "0.4";
  dragSrcEl = this;
  e.dataTransfer.setData("text/html", this.innerHTML);
}

function dragLeave(e) {
  this.classList.remove("over");
}

function dragOver(e) {
  e.preventDefault();
  this.classList.add("over");
}

function dragDrop(e) {
  if (dragSrcEl != this) {
    dragSrcEl.innerHTML = this.innerHTML;
    this.innerHTML = e.dataTransfer.getData("text/html");
  }
  this.classList.remove("over");
}

function dragEnd(e) {
  this.style.opacity = "1";
  this.classList.remove("over");
  var changedItem = document.querySelector(".item");
}
</script>

그렇다면 현재 요구 사항은 두 개나 해결했다.

  • drag and drop이 되어야한다.
  • 이 때 사용자가 알 수 있게 선택한 항목에 대해 css가 변경되어야한다.
  • 데이터가 바뀌었다면 이 때마다 백엔드에 api로 요청해야한다.

마지막 데이터가 바뀌었다면 백엔드로 api를 요청하는 요구사항이 남았다.
저장 버튼을 두고 싶었지만 요구사항이라 어쩔 수 없었다 😅

Vue에서의 변화를 감지하는 보통의 방법은 watch 다. 그렇지만 위의 코드에는 굳이 watch가 필요없다.
왜냐하면 둘의 태그가 같은지를 감지하고 같다면 dragOver를 실행하기에 이 시점에서 데이터 구조를 바꾸고 이를 찍어보면 이게 바뀐 값이다.

완성! 🎉

참고
HTML 드래그 앤 드롭 API
HTML5 드래그 앤 드롭 API
드래그 앤 드롭(Drag and Drop) 기능 이해 & 구현하기
출처: https://inpa.tistory.com/entry/드래그-앤-드롭-Drag-Drop-기능 [Inpa Dev 👨‍💻:티스토리]

profile
혜삐월드

0개의 댓글

관련 채용 정보