본문 바로가기
React/Framer Motion

[Framer-motion] Drag and Drop

by Rayched 2025. 4. 30.

 

목차
1. Reorder
_① <Reorder.Group>
_② <Reorder.Item>
2. Drag and Drop 예제
_① 예제 1 (Framer-motion / Reorder 기능 활용한 버전)
_② 예제 2 (Reorder 사용하지 않고, DnD 구현)
📔 Reference


이번에 공부한 것은 Framer-motion 라이브러리의 Reorder라는 기능을 활용해서

아래와 같은 Drag and Drop 애니메이션을 구현해보는 방법에 대해 공부해봤다.

Drag and Drop 예시

 

위의 예시는 실제로 Framer-motion의 Reorder 기능을 활용해서 만든 DnD 예제이다.

예제에 대한 설명에 들어가기 앞서서, Reorder에 대한 설명부터 하겠다.


1. Reorder

Framer MotionReorder는 드래그되는 목록을 만드는데 사용할 수 있는 컴포넌트로

<Reorder.Group><Reorder.Item> 두 가지 컴포넌트를 사용한다.


 

① <Reorder.Group>

  • 드래그할 아이템을 모아두는 Container
  • 인자로 valuesonReorder를 전달받는다.

(1). values

  • 드래그 Item들이 참조할 배열 state를 전달 받는다.
  • 인자로 전달된 배열을 통해서, 드래그 Item의 배치 순서가 결정된다.

(2). onReorder

  • 드래그 목록을 제어하는 함수를 전달받는 props
  • 'values'에 전달한 배열 state의 setState() 메서드를 전달
  • 드래그가 발생하면 이에 맞춰서 배열 state의 요소를 재정렬하여
  • 드래그 Item의 순서를 재배치하는 역할을 자동으로 처리한다고 생각하자.

 


② <Reorder.Item>

  • 드래그 Item, 드래그 될 컴포넌트
  • 인자로는 value를 전달받는다.
  • value: 각 드래그 Item이 참조할 값을 전달받는다.
    • values에 전달된 배열 state의 개별 요소
    • 드래그에 따라 요소 재배치할 때 참조할 값

2. Drag and Drop 예제

① 예제 1 (Framer-motion / Reorder 기능 활용한 버전)

 

앞에서 설명한 것만으론 이해하는게 쉽지 않으니 예제를 가져와봤다.

(소스코드는 공식문서에 있는 예제 코드를 참고하였다.)

const Wrapper = styled.div`...`;
const Box = styled(motion.div)`...`;
const DragItems = styled(motion.div)`...`;

function DnDExam(){
    const [items, setItems] = useState([1, 2, 3, 4, 5]);

    useEffect(() => console.log(items));

    return (
        <Wrapper>
            <Box>
                <Reorder.Group axis="y" values={items} onReorder={setItems}>
                    {
                        items.map((item) => {
                            return (
                                <Reorder.Item key={`item${item}`} value={item}>
                                    <DragItems key={item}>
                                        <span>{"Item 0" + item}</span>
                                    </DragItems>
                                </Reorder.Item>
                            );
                        })
                    }
                </Reorder.Group>
            </Box>
        </Wrapper>
    );
};

예제를 실행하면 별도의 설정이나 이런 거 없이 간편하게 드래그 앤 드롭이 구현된 것을 확인할 수 있다.

DnD 예제 실행 예시, 개발자 Console로 Items 배열 상태 확인

화면에 출력된 아이템(DragItem)을 드래그하는 액션을 취하면

Framer-motion에서 자동으로 각 아이템에 대응하는 Items 배열 요소를 재정렬하고

이를 통해서 드래그 앤 드롭이 구현되는 것을 알 수 있다.

 

전체적인 작동 원리를 다 이해하지는 못했기에, Reorder를 사용하지 않고

드래그 앤 드롭을 구현하는 방법에 대해 궁금해져서 다른 방법을 또 찾아봤고

괜찮은 구현 방법을 보게되어서 일단은 여기에 남겨두기로 했다.


② 예제 2 (Reorder 사용하지 않고, DnD 구현)

function DnDExam_old(){
    const [Items, setItems] = useState([1, 2, 3, 4, 5]);
    const dragItem = useRef(); 
    //Drag할 Item Index
    const dragOverITem = useRef(); 
    //드랍할 위치에 있는 Item의 Index

    const DragInit = (idx: any) => {
        console.log(dragItem);
        dragItem.current = idx;
        //드래그한 Item의 index 저장
        console.log(dragItem);
        console.log(idx);
    };

    const DragEnter = (idx: any) => {
        console.log(dragOverITem);
        dragOverITem.current = idx;
        //드래그한 Item이 접근한 영역의 index 저장
        console.log(dragOverITem);
        console.log(idx);
    };

    const ItemDrop = (e: any) => {
        const newList = [...Items];
        //기존 Items의 사본 생성, 저장
        const dragItemValue = newList[Number(dragItem.current)];
        //Items의 사본에서 드래그 한 아이템과 동일한 요소 가져옴

        newList.splice(Number(dragItem.current), 1);
        //Items의 사본에서 드래그한 아이템이 들어갈 위치에 있는
        //기존 요소를 삭제한다.
        newList.splice(Number(dragOverITem.current), 0, dragItemValue);
        //그리고 기존 요소를 삭제해서 빈 자리가 된 부분에
        //앞에서 추출해온 데이터를 추가한다.

        dragItem.current = undefined;
        dragOverITem.current = undefined;
        //드래그한 아이템의 index, 드래그 할 위치의 index
        //다음을 위해 저장해둔 정보 삭제하고 undefined로 초기화

        setItems(newList);
    };

    return (
        <Wrapper>
            <Box>
                {
                    Items.map((item, idx) => {
                        return (
                            <DragItems 
                                key={item}
                                draggable
                                onDragStart={() => DragInit(idx)}
                                onDragEnter={() => DragEnter(idx)}
                                onDragOver={(e) => e.preventDefault()}
                                onDragEnd={ItemDrop}
                            >
                                <span>{"Item 0" + item}</span>
                            </DragItems>
                        );
                    })
                }
            </Box>
        </Wrapper>
    );
};

예제를 실행하면 이런 모습이다. 따로 애니메이션을 적용하지 않은 것도 있기는 하지만

Reorder를 사용한 버전과 비교했을 때 사뭇 다른 모습으로 동작한다는 걸 알 수 있다.

따로 애니메이션 효과를 설정하지는 않았다.

예제에서 사용된 draggable, onDragStart, onDragEnter, onDragEnd, onDragOver

Drag and Drop 기능을 구현하는데 사용됐던 속성과 Event에 대해 간략하게 정리해봤다.

1. draggable
- HTML 요소를 드래그가 가능하게 해주는 속성
- draggable="true" / draggable="false"와 같이 작성해서 사용한다.

2. DragStart
- 사용자가 요소나 텍스트를 드래그하기 시작했을 때 발생되는 Event

3. DragEnter
- 사용자가 드래그한 요소가 드롭 가능한 영역에 진입했을 때 발생되는 Event

4. DragEnd
- 사용자의 드래그 작업이 종료됐을 때 발생되는 Event

5. DragOver
- 사용자가 드래그 중인 요소가 다른 요소 위로 올라오거나
  아니면 다른 요소 위를 지나갈 때마다 발생되는 Event


예제의 대략적인 실행 과정을 정리해보자면 다음과 같이 나온다.

1. 사용자가 특정 Item을 드래그 시작함
- DragStart Event 실행, 해당 Event는 DragInit(idx) 함수를 실행시킨다.
- DragInit 함수는 인자로 전달받은 값, 드래그한 아이템의 index를
  dragItem 객체의 current prop에 저장한다.
 (dragItem에는 useRef() Hook의 결과 값인 {current: undefined}가 저장된 상태)

2. 사용자가 드래그한 Item이 Drop 가능한 영역에 진입
- DragEnter Event 실행, 해당 Event는 DragEnter(idx) 함수를 실행한다.
- DragEnter 함수는 인자로 전달받은, 드롭할 영역에 있는 Item의 index를
dragOverItem 객체의 current prop에 저장한다.
(dragOverItem에는 useRef() Hook의 결과 값인 {current: undefined}가 저장된 상태)

3. 드래그한 Item을 Drop한다.
- DragEnd Event 실행, 해당 Event는 ItemDrop() 함수를 실행한다.

- 해당 함수의 동작 과정은 다음과 같다.
 ① 변수 newList에 Items 배열을 저장한다. (Items 배열의 각 요소는 Drag 요소에 대응한다.)
 ② 변수 dragItemValue에 드래그한 아이템에 대응하는 Items 배열의 요소를 저장해둔다.
 ③ Array.splice(), newList 배열에서 드래그한 아이템과 동일한 index를 가진 요소 삭제한다.
 ④ Array.splice(), newList 배열에서 드롭할 영역에 있는 아이템 뒤에
     dragItemValue에 저장해둔 값, 드래그한 아이템의 사본을 추가한다. (삭제 X)
     => 드래그 한 아이템과 드롭할 영역의 아이템의 순서를 바꾸는 로직
 ⑤ dragItem, dragOverItem의 current prop의 값을 undefined로 초기화
 ⑥ newList 배열을 'Items' 상태에 저장한다.
      state인 Items에 Update가 발생했으니, Re-rendering 진행
      Items 배열을 참조하는 드래그 요소의 순서에도 변화가 발생한다.

 - 통상적으론 기존 요소 위에 드래그한 요소의 Drop이 불가능하지만
   DragOver Event에 e.preventDefault() 설정해뒀기 때문에 Drag and Drop이 가능하다.


📔 Reference

공식 문서 / HTML 드래그 앤 드롭 API
Framer-motion 공식 문서 / Reorder
React에서 드래그 앤 드랍 직접 구현하기 / Tistory 지식물원
(Reorder 사용 X 예제 소스코드 작성할 때 참고하였음.)

'React > Framer Motion' 카테고리의 다른 글

Framer Motion 맛보기  (0) 2025.01.01