기술 메모장

입사 후 첫 버그를 잡다! HTML Drag&Drop API

ghoon99 2022. 7. 24. 12:57

첫 버그를 잡아보자

입사 후 간단한 웹뷰 페이지를 클론하는 과제로 진행하였고

이후 사내에서 사용하는 컨텐츠 관리 페이지(CMS)를 담당하게 되었다.

 

처음에는 코드를 분석하면서 나에게 새로운 기술스택(Ant Design,Typescript) 에 익숙해지는 연습을 하였다.

그러던 도중 처음으로 버그를 고쳐달라는 요청사항이 들어왔다.

 

드래그&드롭 으로 어떤 컨텐츠의 순서를 변경하는 리스트의 드래그 기능이 아예 동작을 하지 않았던 것이다.

아예 동작을 하지 않았던데 왜 이제 발견되었는지 모르겠다.. 아마 자주 쓰는 기능은 아니였나보다.

 

해당 부분의 코드를 살펴보니 라이브러리 없이 HTML Drag&Drop API 로 구현이 되어있었고 

간단한 기능인만큼 빠르게 공부하여 라이브러리 없이 직접 만들어 고치기로 하였다.

 

HTML Drag&Drop API

HTML5 부터 웹 페이지 내 모든 요소가 드래그 될 수 있고, 드래그&드롭 API 를 지원한다고 한다.

 

li(3번) 요소를 드래그해서 끌어왔다.

HTML 요소에 draggable 속성을 주어 요소를 드래그 할 수 있게 만들 수 있다.

드래그 할 수 있는 요소를 드래그 하면 드래그 이벤트가 발생한다.

 

내가 원하는 기능을 구현하기 위해 알아야 했던 이벤트 이외에도 다양한 드래그 이벤트들이 존재한다. (MDN 참고)

draggable (속성) 대상이 드래그가 가능한 요소로 만들 수 있게 한다.
dragstart 요소를 드래그하기 시작했을 때 발생한다.
dragenter 드래그 중 마우스가 어떤 요소 위로 진입할 때 발생한다.
dragover 드래그하면서 마우스가 대상 객체의 위에 자리 잡고 있을 때 발생함 (drop 이벤트를 발생하게 하려면 preventDefault)
drop 드래그가 끝나 드래그하던 요소를 놓는 장소에 위치한 (drop이벤트가 가능한)요소에서 발생함.

 

간단하게 JS 로 드래그&드롭을 구현해보았다.

 

 

With React

구현해야 할 것 

- 어떤 데이터들을 불러와서 리스트로 보여준다.

- 리스트들은 드래그&드롭으로 순서를 변경 할 수 있다.

- 순서가 바뀔 때마다 state 로 다시 반영이 된다 (저장을 누르면 현재의 state 로 네트워크 요청을 보내야하기 때문)

 

드래그&드롭으로 어떤 리스트 요소의 순서를 바꾸면 화면에도 반영이 되고 상태값도 반영이 되어야 한다.

 

첫번째 방법

 

// 배열과 2개의 인덱스를 받아 요소가 교환된 새 배열을 반환
const swapElements = <T extends unknown>(array: T[], points: TPoints): T[] => {
  const { start, end } = points;
  const newArray = [...array];
  [newArray[start], newArray[end]] = [newArray[end], newArray[start]];
  return newArray;
};

const App = () => {
  const [points, setPoints] = useState<TPoints>({ start: 0, end: 0 });
  const [state, setState] = useState(sampleData);
	
  // 드래그 시작, 잡은 요소의 인덱스 저장
  const handleDragStart = (event: React.DragEvent<HTMLLIElement>) => {
    const startIndex = event.currentTarget.dataset.index!;
    setPoints((prev) => ({ ...prev, start: Number(startIndex) }));
  };
  // 드래그 끝(드롭), 놓을 때 위치와 잡은 요소의 인덱스로 배열안 요소 교환
  const handleDrop = (event: React.DragEvent<HTMLLIElement>) => {
    const endIndex = event.currentTarget.dataset.index!;

    const newState = swapElements(state, {
      start: points.start,
      end: Number(endIndex)
    });

    setState(newState);
    console.log("newState!! :", newState.map((item) => item.title).join(" "));
  };

  return (
    <ul>
      {state.map((item, index) => (
        <li
          data-index={index}
          draggable
          onDragOver={(event) => event.preventDefault()}
          onDragStart={handleDragStart}
          onDrop={handleDrop}
          key={item.id}
        >
          {item.title}
        </li>
      ))}
    </ul>
  );
};

export default App;

 

onDragStart 와 onDrop 을 이용하여

리스트 요소를 잡았을 때(start) 와 놓았을 때의 위치(end)를 Points 에 저장하였다.

그리고 Drop 이벤트가 일어날 때 저장된 start,end 를 이용하여 둘의 위치를 서로 바꾸었다.

 

 

원하는 대로 동작은 하는데 뭔가 아쉬웠다.

드래그를 하고 있는 동안에는 실시간으로 리스트의 순서가 변경되지 않았고 드래그가 끝나야지 리스트의 순서가 반영이 되었다.

그래서 실시간으로 리스트의 순서를 반영하게 코드를 고쳐보았다.

 

실시간으로 반영되지 않는 리스트 (좌), 실시간으로 반영되는 리스트(우)

두번째 방법

 

  // ... handleDragStart 까진 위와 동일 
  
  // 드래그 하는 도중 다른 요소를 만나면 실행 
  const handleDragEnter = (event: React.DragEvent<HTMLLIElement>) => {
    const endIndex = event.currentTarget.dataset.index!;
	// 미리 순서를 바꿔놓기 
    const newState = swapElements(state, {
      start: points.start,
      end: Number(endIndex)
    });

    setState(newState);
    // 이때부터는 start는 현재 요소가 위치한 곳 
    setPoints((prev) => ({ ...prev, start: Number(endIndex) }));
    console.log("newState!! :", newState.map((item) => item.title).join(" "));
  };
 
  // 실시간으로 바꿔줬기 때문에 drop 이벤트에서 할 것은 없음
  return (
    <ul>
      {state.map((item, index) => (
        <li
          data-index={index}
          draggable
          onDragOver={(event) => event.preventDefault()}
          onDragStart={handleDragStart}
          onDragEnter={handleDragEnter}
          key={item.id}
        >
          {item.title}
        </li>
      ))}
    </ul>
  );

 

이번에는 onDragEnter 를 이용하여 드래그 한 요소가 다른 요소위로 올라오는 순간을 이용하였다.

 

드래그 할 때 잡고 있는 요소가 다른 요소 위로 올가오는 순간 둘을 맞바꾸게 하면 

드래그 하는 동안에도 리스트의 순서가 바뀌는 것을 반영 할 수 있게 되었다.

 

 

마무리 

이렇게 처음 버그를 고치면서 드래그&드롭 API 를 공부하고 적용해볼 수 있었다.

일단 빠르게 고쳐야 했으므로 당장 문서를 보고 공부하며 생각나는 방식으로 구현을 해보았는데 더 괜찮은 방법이 분명 있을 것이다.

 

 

또 다른 드래그&드롭으로 정렬할 수 있는 리스트 예제를 찾아보니 요소를 옮길때 애니메이션도 들어가 있는 것을 확인 할 수 있었다.

그것 또한 나중에 한번 구현을 해봐야겠다.

 

후에 찾아보니 react DND , react sortable hoc 등등 드래그&드롭 리스트에 자주 사용되는 라이브러리가 있다고도 한다.

HTML 에서 지원하는 API 들을 알았으니 라이브러리를 이용할 때 더 잘 이해하고 사용할 수 있을 지도 모르겠다.

 

다음 글은 모달 컴포넌트를 직접 만들면서 배운 것들에 대한 내용을 작성할 예정이다.

 

1편 끝!  

 

 

참고자료

http://tcpschool.com/html/html5_api_dragAndDrop

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com

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

 

HTML 드래그 앤 드롭 API - Web API | MDN

HTML 드래그 앤 드롭 인터페이스는 파이어폭스와 다른 브라우저에서 어플리케이션이 드래그 앤 드롭 기능을 사용하게 해줍니다.

developer.mozilla.org