React 라이브러리 없이 drag and drop 기능 구현하기

Jungmin Lee·2022년 3월 15일
5

원티드X코드스테이츠 프리온보딩 코스를 진행하는 중인데, 이번 기업 과제에서 drag and drop 기능 구현을 담당하였습니다.
라이브러리를 사용하면 손쉽게 구현이 가능하겠지만, 시간적 여유도 있고 어떤 원리로 작동하는 지 궁금해서 라이브러리 없이 구현하기로 하였습니다.

먼저 HTML 드래그 이벤트 속성에 대해 알아보면 아래 표와 같습니다.

1. 드래그 할때 사용할 초기값 세팅 및 props 설정

 // dragAndDrop 초기값
  const [dragAndDrop, setDragAndDrop] = useState({
    draggedFrom: null,
    draggedTo: null,
    isDragging: false,
    originalOrder: [],
    updatedOrder: [],
  });

dragAndDrop state는 드래그로 변경될 내용들을 담아줄 객체 상태입니다.
draggedFrom은 드래그 시작하는 인덱스이고, draggedTo는 변경될 드래그 인덱스를 할당해줍니다.
originalOrder 같은 경우는 dragAndDrop에 사용할 배열 list 목록을 할당하고, updatedOrder는 새롭게 생성된 dragAndDrop배열을 할당해줍니다.

 <ul>
      {list.map((item, index) => {
        return (
          <li
            style={{
              display: "flex",
              justifyContent: "center",
              alignItems: "center",
            }}
            className="draggable"
            key={index}			
            draggable={true} 				//  draggable => true이면 드래그가 가능합니다.
            data-position={index}			//  dataset에 index값을 주어 선택된 index를 찾을 수 있습니다.
            onDragStart={onDragStart}		//  ex) event.currentTarget.dataset.position
            onDragOver={onDragOver}
            onDragLeave={onDragLeave}
            onDrop={onDrop}
            onDragEnter={onDragEnter}
            onDragEnd={onDragEnd}
          >
            <span>{item.emoji}</span>
            <p>{item.name}</p>
          </li>
        );
      })}
    </ul>

2. onDragStart 드래그가 시작했을 때 발생하는 이벤트

 // 사용자가 객체(object)를 드래그하려고 시작할 때 발생함.
  const onDragStart = (event) => {
    event.currentTarget.style.opacity = "0.4";
    const initialPosition = parseInt(event.currentTarget.dataset.position);
    setDragAndDrop({
      ...dragAndDrop,
      draggedFrom: initialPosition,
      originalOrder: list,
    });
  };

initialPosition는 드래그를 시작 했을 때의 배열의 인덱스를 반환합니다.
setDragAndDrop를 통해 시작한 드래그의 인덱스 값(draggedFrom)과 드래그 리스트에 사용할 배열(originalOrder)를 담은 객체로 반환합니다.

3. onDragOver 드래그로 인해 item들이 겹쳐졌을 때 발생하는 이벤트 ( milli sec마다 발생함)

  // 드래그하면서 마우스가 대상 객체의 위에 자리 잡고 있을 때 발생함.
  const onDragOver = (event) => {
    event.preventDefault();
    let newList = dragAndDrop.originalOrder;						   
    const draggedFrom = dragAndDrop.draggedFrom;					   // 드래그 되는 항목의 인덱스(시작)
    const draggedTo = parseInt(event.currentTarget.dataset.position);  // 놓을 수 있는 영역의 인덱스(끝)
    const itemDragged = newList[draggedFrom];						   
    const remainingItems = newList.filter(			// draggedFrom(시작) 항목 제외한 배열 목록 
      (item, index) => index !== draggedFrom
    );
    newList = [										// 드래그 시작, 끝 인덱스를 활용해 새로운 배열로 반환해줌
      ...remainingItems.slice(0, draggedTo),
      itemDragged,
      ...remainingItems.slice(draggedTo),
    ];
    if (draggedTo !== dragAndDrop.draggedTo) {		// 놓을 수 있는 영역이 변경 되면 객체를 변경해줌
      setDragAndDrop({
        ...dragAndDrop,
        updatedOrder: newList,
        draggedTo: draggedTo,
      });
    }
  };

먼저 draggedFrom과 draggedTo로 드래그 시작,끝 index를 찾아 냅니다.
다음으로 remainingItems를 통해 시작한 인덱스를 제외한 배열을 만들어 준다음 newList를 만들어줍니다.
draggedTo의 값의 변경되면 위치가 바뀐것으로 인지하여 setDragAndDrop로 새로운 객체로 변경해줍니다.

4. onDrop 잡은 Item을 적절한 곳에 놓았을 때 발생하는 이벤트 (다른 item이랑 겹쳐졌을 때)

  const onDrop = (event) => {
    setList(dragAndDrop.updatedOrder);
    setDragAndDrop({
      ...dragAndDrop,
      draggedFrom: null,
      draggedTo: null,
    });
  };

드래그가 끝나 dragAndDrop 상태를 초기화 시켜줍니다.

5. onDragLeave 범위를 벗어나면 발생하는 이벤트

  const onDragLeave = (event) => {
    event.currentTarget.classList.remove("over");
    setDragAndDrop({
      ...dragAndDrop,
      draggedTo: null,
    });
  };

onDrop 마찬가지로 dragAndDrop 상태를 초기화 시켜줍니다.

6. onDragEnter 다른 item이랑 겹쳐졌을 때 발생하는 이벤트 (1번만)

  // 잡은 Item이 다른 Item이랑 겹쳤을 때 발생<겹쳐졌을 때>
  const onDragEnter = (event) => { 
    event.currentTarget.classList.add("over");
  };

ClassName을 주어 css로 커스텀을 할 수 있습니다.

7. onDragEnd 잡은 Item을 놓았을 때 발생하는 이벤트 (다른 item이랑 겹치지 않아도 발생함)

  // 잡은 Item을 놓았을 때 발생
  const onDragEnd = (event) => {
    event.currentTarget.style.opacity = "1";
    const listItens = document.querySelectorAll(".draggable");
    listItens.forEach((item) => {
      item.classList.remove("over");
    });
  };

css로 커스텀을 위해 작성된 코드입니다.

완성된 drag and drop

profile
Front-end developer who never gives up.

0개의 댓글