짧은 글

[react-sortablejs] 그리드 형태 드래그앤드롭 구현 라이브러리

jaeeedev 2023. 2. 17. 17:42

그리드 형태에 개별 요소의 위치를 바꿀수도 있는 기능이 필요했다.

보통은 react-beautiful-dnd를 많이 쓰는것 같은데 내 상황에 맞는 옵션을 잘 모르겠어서(열을 넘나드는 형태는 있는데 나한테 딱 맞는 상황이라고 하기에는 애매했다.)

그리드 예제가 있었던 react-sortablejs 를 사용했다. 리액트 버전은 공식사이트가 없고 깃허브뿐인데 비슷하게 그리드 예제가 있고 공식사이트가 있는 react-sortable-hoc 를 사용해도 좋을 듯. 시각적인 예제들도 있고 사실 저기가 더 다운로드수 많음

설치하기

npm install --save react-sortablejs sortablejs
// 타입스크립트 사용 시
npm install --save-dev @types/sortablejs

# OR
yarn add react-sortablejs sortablejs
// 타입스크립트 사용 시
yarn add -D @types/sortablejs

공식 문서 기본 세팅

import { ReactSortable } from "react-sortablejs";
import { useState } from "react";


export const BasicFunction = (props) => {
  const [state, setState] = useState([
    { id: 1, name: "shrek" },
    { id: 2, name: "fiona" },
  ]);

  return (
    <ReactSortable list={state} setList={setState}>
      {state.map((item) => (
        <div key={item.id}>{item.name}</div>
      ))}
    </ReactSortable>
  );
};

상태를 기반으로 동작한다. 비동기적으로 데이터를 받아온 다음 setState 후 진행해도 좋음

내가 사용한 방법

const MainSub = () => {
  const [list, setList] = useState([
    {
      title: "lorem ipsum",
      children: "test",
    },
    {
      title: "이",
      children: "2",
    },
    {
      title: "삼",
      children: "3",
    },
    {
      title: "사",
      children: "4",
    },
    {
      title: "오",
      children: "5",
    },
    {
      title: "육",
      children: "6",
    },
  ]);

  const onDragDropEnds = (oldIndex, newIndex) => {
    console.log(oldIndex, newIndex);
  }; // 드래그가 끝났을 때 실행할 콜백, oldIndex는 요소가 처음 위치했던 인덱스, newIndex는 새로 들어간 인덱스


  return (
    <Wrapper>
      <ReactSortable
        list={list} 
        setList={(newList) => setList(newList)}
        handle=".dragHandle" // 드래그를 촉발시킬 className 지정
        animation="300" // 요소끼리 자리가 바뀔 때 부드럽게 이동
        className="grid-container" // css 스타일링 하고싶으면 지정
        onEnd={({ oldIndex, newIndex }) => onDragDropEnds(oldIndex, newIndex)}
        /* 드래그가 끝났을 때 실행할 함수 */
      > 
        {list.map((li, i) => (
          <MenuBox key={i} title={li.title} children={li.children} draggable />
          /* 이 컴포넌트의 draggable은 컴포넌트 커스텀용으로 만든 속성, 
          드래그 이벤트나 sortablejs와 관계 x */

        ))}
      </ReactSortable>
    </Wrapper>
  );
};

여기서 map에서 반환하는 컴포넌트는 빠르게 만들려고 키값을 인덱스로 줬는데 실제로 사용할때는 이러지 마세용 ^,,^

const MenuBox = ({ children, title, draggable, ...props }, ref) => {
  const displayTitle = title || "테스트";

  return (
    <Box {...props}>
      <div className="box__title">
        <Title bg="#fff">{displayTitle}</Title>
        <div className="button__box">
          <Button bg={primary.main} color="#fff" fz={"14px"}>
            +More
          </Button>
          {draggable && <DragHandleIcon className="drag-icon dragHandle" />}
          /* ✨dragHandler 클래스 추가✨ */
        </div>
      </div>
      <div className="box__content">
        {children ? children : `No ${displayTitle} contents.`}
      </div>
    </Box>
  );
};

컴포넌트에서 handle=".dragHandle" 형태로 prop을 줬었다. handle에 넣은 클래스명을 지정해주면 그 요소를 잡고 드래그를 할 수 있다. 나는 컴포넌트의 모든 부분이 아니라 오른쪽 상단의 아이콘을 통해서만 움직일 수 있게 하고 싶었기 때문에 아이콘에 dragHandle 클래스를 주었다.

반투명하게 클론된 요소가 따라다니는게 보인다.

HTML5에서는 기본적으로 드래그중인 요소를 클론하고 그 클론한 요소의 opacity를 반투명하게 낮춰버리는 특성이 있다.

하지만 나는 클론된 요소를 진하게 만들고 아래에 남아있는 원본 요소를 보이지 않게 만들고 싶었다. 겹쳐 보이니 헷갈리기도 하고 움직이고 있다는것을 더 명확하게 하고 싶었다.

<Wrapper>
      <ReactSortable
        list={list}
        setList={(newList) => setList(newList)}
        handle=".dragHandle"
        animation="300"
        forceFallback={true} // ✨추가하기✨
        className="grid-container"
        onEnd={({ oldIndex, newIndex }) => onDragDropEnds(oldIndex, newIndex)}
      >
        {list.map((li, i) => (
          <MenuBox title={li.title} children={li.children} draggable />
        ))}
      </ReactSortable>
    </Wrapper>

forceFallback은 HTML이 기본적으로 하는 행동들 (위에서 말한 opacity 낮추기) 을 없애고 이를 강제로 변경할 수 있도록 해준다. fallback은 대체라는 뜻을 가지고 있는데 원래 제공되던 반투명한 요소 대신 대체 요소를 채워 넣어서 마음대로 커스텀 할 수 있게 되는 것이다.

클론되어 딸려올라오는 요소는 .sortable-fallback 이라는 클래스명을 가지고 있다. 이 클래스명을 통해 스타일링을 마음대로 할 수 있다.

또한 현재 드래그중인 원본 요소는 .sortable-chosen 이라는 클래스명을 가진다. 이것을 통해 아래에 있는 요소를 지워주었다.

.sortable-fallback {
   opacity: 1 !important;

   /* 원하는 스타일링 하기 */
  }

.sortable-chosen {
    opacity: 0;
  }

색이 잘 안보여서 배경색을 변경해 주었다. 드래그중인 대상을 인지하는 게 훨씬 쉬워졌다.

아쉬운 점?

윈도우에서는 드래그를 늘였다가 놓았을때 천천히 제자리로 돌아가는 애니메이션이 되지 않는 것 같다.

react-beautiful-dnd 에서는 된다. 이건 원하지 않는 사람도 있으니 필요한대로 선택하면 될듯