HTML 드래그 앤 드롭 API를 이용하여 사용자는 draggable요소를 마우스로 선택해 droppable 요소로 드래그 하고 마우스 버튼에서 손을 뗌으로써 요소를 드롭할 수 있다. 드래그 하는 동안 draggable 요소는 반투명한 채로 마우스 포인터를 따라다닌다.
드래그 이벤트
드래그 앤 드롭은 DOM event model과 drag events를 mouse events로부터 상속받는다.
draggable 요소를 마우스로 선택하고, 마우스 포인터를 droppable요소로 가져가 마우스 버튼을 떼는 것으로 이루어진다.
다음과 같은 드래그와 관련된 속성이 있다.
이벤트 | 이벤트 핸들러 | 설명 |
---|
drag | ondrag | 요소나 텍스트 블록을 드래그 할 때 발생한다. |
dragstart | ondragstart | 사용자가 요소나 텍스트 블록을 드래그하기 시작했을 때 발생한다. |
dragend | ondragend | 드래그를 끝냈을 때 발생한다. (마우스 버튼을 떼거나 ESC 키를 누를 때 |
dragenter | ondragenter | 드래그한 요소나 텍스트 블록을 적합한 드롭 대상위에 올라갔을 때 발생한다. |
dragexit | ondragexit | 요소가 더 이상 드래그의 직접적인 대상이 아닐 때 발생한다. |
dragleave | ondragleave | 드래그하는 요소나 텍스트 블록이 적합한 드롭 대상에서 벗어났을 때 발생한다. |
dragover | ondragover | 요소나 텍스트 블록을 적합한 드롭 대상 위로 지나갈 때 발생한다. (짧은 간격의 ms마다 한 번씩 발생한다) |
drop | ondrop | 요소나 텍스트 블록을 적합한 드롭 대상에 드롭했을 때 발생한다. |
드래그 앤 드롭 적용하기
드래그 앤 드롭을 리액트에 적용해보았다.
1 요소를 draggable로 설정하기
요소를 만들고 요소를 draggable로 설정해주어야 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| import React, { useState, useRef } from "react"; import "./App.css";
const App = () => { const [list, setList] = useState([ "Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6", ]);
return ( <> {list && list.map((item, index) => ( <div style={{ backgroundColor: "lightblue", margin: "20px 25%", textAlign: "center", fontSize: "40px", }} key={index} draggable > {item} </div> ))} </> ); }; export default App;
|
2 드래그 시작 이벤트 추가하기
드래그가 시작될 때 어떤 요소로부터 시작되었는지 추적해야한다.
즉 dragStart이벤트의 target을 추적해야한다.
useRef를 사용하여 드래그가 시작된 아이템을 저장하였다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import React, { useState, useRef } from "react"; import "./App.css";
const App = () => { const dragItem = useRef(); const [list, setList] = useState([ "Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6", ]);
const dragStart = (e, position) => { dragItem.current = position; };
return ( <> {list && list.map((item, index) => ( <div style={{ backgroundColor: "lightblue", margin: "20px 25%", textAlign: "center", fontSize: "40px", }} onDragStart={(e) => dragStart(e, index)} key={index} draggable > {item} </div> ))} </> ); }; export default App;
|
3 드래그 하는 동안 건너뛰는 다른 요소 추적하기
드래그 하는 동안 다른 드래그 가능한 요소를 지날 경우 이 요소를 저장해야 한다.
즉 dragenter가 제일 최근에 일어난 요소를 저장해야한다.
useRef를 사용하여 저장하였다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| import React, { useState, useRef } from "react"; import "./App.css";
const App = () => { const dragItem = useRef(); const dragOverItem = useRef(); const [list, setList] = useState([ "Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6", ]);
const dragStart = (e, position) => { dragItem.current = position; };
const dragEnter = (e, position) => { dragOverItem.current = position; };
return ( <> {list && list.map((item, index) => ( <div style={{ backgroundColor: "lightblue", margin: "20px 25%", textAlign: "center", fontSize: "40px", }} onDragStart={(e) => dragStart(e, index)} onDragEnter={(e) => dragEnter(e, index)} key={index} draggable > {item} </div> ))} </> ); }; export default App;
|
4 드래그가 끝났을 경우의 이벤트 추가하기
앞서 useRef에 저장해둔 두 요소를 바꾸는 이벤트를 추가한다.
즉 dragEnd가 되었을 때의 이벤트를 추가한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| import React, { useState, useRef } from "react"; import "./App.css";
const App = () => { const dragItem = useRef(); const dragOverItem = useRef(); const [list, setList] = useState([ "Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6", ]);
const dragStart = (e, position) => { dragItem.current = position; };
const dragEnter = (e, position) => { dragOverItem.current = position; };
const drop = (e) => { const copyListItems = [...list]; const dragItemContent = copyListItems[dragItem.current]; copyListItems.splice(dragItem.current, 1); copyListItems.splice(dragOverItem.current, 0, dragItemContent); dragItem.current = null; dragOverItem.current = null; setList(copyListItems); };
return ( <> {list && list.map((item, index) => ( <div style={{ backgroundColor: "lightblue", margin: "20px 25%", textAlign: "center", fontSize: "40px", }} onDragStart={(e) => dragStart(e, index)} onDragEnter={(e) => dragEnter(e, index)} onDragEnd={drop} key={index} draggable > {item} </div> ))} </> ); }; export default App;
|
5 dragover시 기본 애니메이션 효과를 제거한다.
dragover에 preventDefault를 호출해야 드롭을 허용할 수 있다.
정확히는 드롭이 허용된 애니메이션을 줄 수 있다.
1
| onDragOver={(e) => e.preventDefault()}
|
++ dragEnter 될 떄마다 바뀌도록 하기
dragEnter 핸들러와 dragEnd 핸들러를 합치고 요소가 바뀔 때마다 dragItem이 dragOverItem을 참조하도록 하여 dragEnter 이벤트에 배열이 바뀌도록 설정할 수도 있다.
요소가 다른 요소를 통과할 때마다 배열이 바뀐다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| function App() { const dragItem = useRef(null); const dragOverItem = useRef(null); const [list, setList] = useState([ "Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6", "Item 7", ]);
const dragStart = (e, position) => { dragItem.current = position; };
const dragEnter = (e, position) => { dragOverItem.current = position; const copyListItems = [...list]; const dragItemContent = copyListItems[dragItem.current]; copyListItems.splice(dragItem.current, 1); copyListItems.splice(dragOverItem.current, 0, dragItemContent); dragItem.current = dragOverItem.current; dragOverItem.current = null; setList(copyListItems); };
return ( <> {list && list.map((item, index) => ( <div style={{ backgroundColor: "lightblue", margin: "20px 25%", textAlign: "center", fontSize: "40px", }} onDragStart={(e) => dragStart(e, index)} onDragEnter={(e) => dragEnter(e, index)} // onDragEnd={drop} onDragOver={(e) => e.preventDefault()} key={index} draggable > {item} </div> ))} </> ); }
export default App;
|