목차
1. Reorder
_① <Reorder.Group>
_② <Reorder.Item>
2. Drag and Drop 예제
_① 예제 1 (Framer-motion / Reorder 기능 활용한 버전)
_② 예제 2 (Reorder 사용하지 않고, DnD 구현)
📔 Reference
이번에 공부한 것은 Framer-motion 라이브러리의 Reorder라는 기능을 활용해서
아래와 같은 Drag and Drop 애니메이션을 구현해보는 방법에 대해 공부해봤다.
위의 예시는 실제로 Framer-motion의 Reorder 기능을 활용해서 만든 DnD 예제이다.
예제에 대한 설명에 들어가기 앞서서, Reorder에 대한 설명부터 하겠다.
1. Reorder
Framer Motion의 Reorder는 드래그되는 목록을 만드는데 사용할 수 있는 컴포넌트로
<Reorder.Group>과 <Reorder.Item> 두 가지 컴포넌트를 사용한다.
① <Reorder.Group>
- 드래그할 아이템을 모아두는 Container
- 인자로 values와 onReorder를 전달받는다.
(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>
);
};
예제를 실행하면 별도의 설정이나 이런 거 없이 간편하게 드래그 앤 드롭이 구현된 것을 확인할 수 있다.
화면에 출력된 아이템(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
- 사용자가 요소나 텍스트를 드래그하기 시작했을 때 발생되는 Event3. DragEnter
- 사용자가 드래그한 요소가 드롭 가능한 영역에 진입했을 때 발생되는 Event4. DragEnd
- 사용자의 드래그 작업이 종료됐을 때 발생되는 Event5. 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 |
---|