Drag & Drop 를 이용해서 리스트 요소 순서 바꾸기

MDN - HTML 드래그 앤 드롭 API

MDN - Drag Operations(실습)

HTML SPEC - Drag and drop

Codepan example

How to Implement Drag and Drop Feature for Your React Component

todo list 를 구현했는데, 뭔가 너무 아쉬웠다. 평소에 구현해보고 싶었던 기술중에 Drag & Drop 이 있었는데 todo list 에 적용하기 너무 좋아보여서 시도했다.

인터페이스

DnD 의 인터페이스로는 크게 4가지가 존재한다.

DateEvetDataTranfer 만 알아보자.

DragEvent

이벤트

이벤트 핸들러

설명

dragstart (en-US)

사용자가 요소나 텍스트 블록을 드래그하기 시작했을 때 발생한다. (드래그 시작하기를 보시오.)

요소나 텍스트 블록을 드래그 할 때 발생한다.

dragend (en-US)

드래그를 끝냈을 때 발생한다. (마우스 버튼을 떼거나 ESC 키를 누를 때) (드래그 끝내기를 보시오)

dragenter (en-US)

드래그한 요소나 텍스트 블록을 적합한 드롭 대상위에 올라갔을 때 발생한다. (드롭 대상 지정하기를 보시오.)

dragover (en-US)

요소나 텍스트 블록을 적합한 드롭 대상 위로 지나갈 때 발생한다. (매 수백 밀리초마다 발생한다.)

dragleave (en-US)

드래그하는 요소나 텍스트 블록이 적합한 드롭 대상에서 벗어났을 때 발생한다.

drop (en-US)

요소나 텍스트 블록을 적합한 드롭 대상에 드롭했을 때 발생한다. (드롭하기를 보시오.)

dragexit

요소가 더 이상 드래그의 직접적인 대상이 아닐 때 발생한다.

Drag 관련 이벤트. 여기서 중요한건 이벤트의 발동 주체가 2가지로 나뉜다는것임.

1. 드래그 요소

마우스로 클릭하고 끌고 다니는 요소

  • drag

  • dragStart

  • dragEnd

2. 드래그 요소와 겹치는 아이템

Define a drop zone

  • dragEnter

  • dragOver (필수로 구현)

  • dragLeave

  • drop (필수로 구현)

여기서 주목할 건 Drag 요소를 다른 요소 어딘가에 놓으면 그에 대한 이벤트는 drop 이 발생하고 이 drop 을 핸들링 하는 곳이 Drag 된 요소가 아니라 그 요소와 겹치는 아이템이라는 것.

여기서 의문이 생긴다. Drag 된 요소가 아니라 그 요소가 떨어진 곳에서 Drop 이벤트 핸들러를 실행한다면, Drag 된 요소의 정보는 어떻게 넘겨주는거지? 그 의문은 DataFransfer 에서 해결된다.

DataTransfer

https://developer.mozilla.org/ko/docs/Web/API/DataTransfer

get, set 이외에도 여러 메서드, 프로퍼티가 있다. 필요하면 위의 링크로 가서 보자.

실제로 드래그를 이용해서 리스트 요소의 순서를 바꾸는걸 생각해보자.

  1. 요소를 마우스로 잡는다.

    • 마우스로 잡은 요소에서 dragStart 이벤트를 핸들링한다.

  2. 끌고 다닌다.

  3. 원하는 곳에 내려놓는다.

    • 내려놓은 곳의 요소에서 drop 이벤트가 발동된다.

우리는 1번에서 요소에 대한 정보를 어딘가에 저장해야하고, 그 정보를 3번에서 이용해야한다. 이걸 해주는게 DataTransfer다. 사용법은 매우 간단하다. setData 를 통해 데이터를 저장하고, getData 를 이용해서 저장된 정보를 불러온다.

여기를 참고하자

실제 사용은 아래처럼 하면된다.

...
// 1번. drag 요소의 데이터를 저장
const handleDragStart = (e) => {
  // key는 getData 에서 사용할 key
  // value 는 저장할 데이터
  e.dataTransfer.setData([key], [value]);
}

// 3번. 놓임 당한 곳에서 drag 된 요소 가져오기
const handleDrop = (e) => {
  e.dataTransfer.getData([key]);
  .. 추가 작업들...
}
...

localStorage 처럼 get, set 으로 사용하면 된다.

실제 사용하기

todoList 내의 todo 들을 드래그해서 옮기는 코드를 예로 든다. 코드는 여기를 눌러서 확인하자.

아래의 코드는 미래 재사용을 위해 useDragAndDrop 이라는 커스텀훅으로 만들었다.

1. draggable 속성 넣기

지금까지 내가 만든 웹요소들은 드래그가 모두 불가능했었다. 왜? 바로 draggable 속성을 넣지 않았기 때문이다. 즉 draggable 속성을 넣으면 요소들은 드래깅이 가능해진다.

...
return {
  <TodoItemContainer
      draggable
         ...
    >
}

draggable 속성을 넣음으로써 API 를 사용할 수 있게 된다.

2. onDragStart 핸들러

드래그의 시작을 알려준다. 인터페이스 부분에서 설명했다시피 이 부분에서 옮기는 요소에 대한 정보를 저장해야한다.

const handleDragStart = (e: React.DragEvent<HTMLDivElement>) => {
  setisDragging(true); // 1
  e.dataTransfer.effectAllowed = 'move'; // 2
  e.dataTransfer.setData('text/plain', id); // 3
};

코드를 분석해보자면

  1. setIsDragging

    • 현재 todo 가 드래그 하는 중이라는걸 알려주는 state 다.

    • 이 값을 통해 todo 는 자신이 드래깅중인지 또는 아닌지를 알 수 있다.

      • css 핸들링에 사용할 수 있다.

  2. e.dataTransfer.effectedAllowed = move

  3. e.dataTransfer.setData('text/plain', id);

    • 드래그 하는 요소의 정보를 담는다.

3. onDragOver 핸들러

Dropzone

tkdghk

const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
  e.preventDefault(); // 1
  setIsDragOver(true); // 2
};
  1. e.preventDefault();

    • 기본으로 발동하는 다른 이벤트들을 막는다. (Touch, pointer events..)

  2. setIsDragOver(true);

    • 현재 요소가 드래그 요소의 아래 깔려있음을 말해준다.

4. onDrop 핸들러

Dropzone

const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
  e.preventDefault();
  setisDragging(false);
  setIsDragOver(false);
  const movingTarget = e.dataTransfer.getData('text/plain'); // 1
  callback(movingTarget, id); // 2
};
  1. const movingTarget = e.dataTransfer.getData('text/plain');

    • 이전에 저장한 데이터를 가져온다.

  2. callback()

    • todo 요소의 위치를 바꾸는 함수다.

Last updated