Drag and Drop (DnD) ๊ตฌํ
๐์์ํ๋ฉฐ
๊ฐ๋จํ ํ ์ด ํ๋ก์ ํธ๋ฅผ ์งํํ๊ณ ์๋ค! ์ฌ์ค ์ฝ๋๋ง ๊ฐ๋จํ ์ฐ์ต์ฉ์ผ๋ก ์จ๋ณด๋ ค๊ณ ํ๋๋ฐ ์ญ์ ๋ญ๋ ๊ฐ์ด ์กํ์ผ(?) ๋ ์ฌ๋ฐ๋ ๋ฒ์ด๋ผโฆ!๐
ํ๋ก์ ํธ๋ ํฌ์ผ๋ชฌAPI์ GraphQL์ ํ์ฉํ๋ ๊ฑด๋ฐ ์ด ๋ฐ์ดํฐ๋ฅผ ์ด๋ป๊ฒ ์ฌ๋ฐ๊ฒ ํ์ฉํด๋ณผ๊น ํ๋ค๊ฐ, ์ด๋ ธ์ ๋ ๋ํ ๋๋ก ํฌ์ผ๋ชฌ ๊ฒ์์ ํ๋ ๊ฒ์ ๋ ์ฌ๋ ค๋ดค๋ค.
ํ๋ ์ด์ด๊ฐ ๊ฐ์ง๊ณ ์๋ ์คํํ ํฌ์ผ๋ชฌ์ ๊ฐ๊ฐ ์์๊ฐ ์์ด์, ๊ฐ์ฅ ๋จผ์ ์๋ฆฌ์ก์ ํฌ์ผ๋ชฌ์ ํ๋ ์ด์ด๋ฅผ ๋ฐ๋ผ๋ค๋๊ฑฐ๋, ๋งจ ์ฒ์์ ๋ฐฐํ ์์๊ฐ ๋๊ณค ํด์ ๋ฐฐํ ์์ ์ ์ ์ด๋ฐ ์์๋ฅผ ๋๋ฆ๋๋ก ์กฐ์ ํ๊ณค ํ๋๋ฐ, ์ด๊ฑธ Drag and Drop์ผ๋ก ์กฐ์ ํ๋ ๊ฒ ๊ฐ๋ค. (์ด๋ ธ์ ๋ ๊ธฐ์ต์ด๋ผ ์ ํํ์ง ์๋คโฆ๐ )
์ด์จ๋ , ์ด๋ฒ์ ์ด๊ฑธ ์ง์ ๊ตฌํํด๋ณด๋ ค๊ณ DnD ๊ตฌํ ๋ฐฉ๋ฒ์ ์ฐพ๋ค๊ฐ, ์์ Web API์์ ์ ๊ณตํ๊ณ ์๋ค๋ ๊ฒ์ ์๊ฒ ๋์๊ณ ! ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ง์๊ณ , ๋๋ฆ๋๋ก ๊ตฌํํ ๋ด์ฉ์ ์ ๋ฆฌํ๊ณ ์ ํ๋ค.
โ drag ์ด๋ฒคํธ
โก๏ธdraggble
drag
์ด๋ฒคํธ๋ ์ฌ์ฉ์๊ฐ ์์๋ ํ
์คํธ๋ฅผ ๋๋๊ทธํ๋ ๋์ ๋งค ์๋ฐฑ ๋ฐ๋ฆฌ์ด๋ง๋ค ๋ฐ์ํ๋ ์ด๋ฒคํธ๋ค. ์ด๋ฐ ์ด๋ฒคํธ๋ ๊ธฐ๋ณธ์ ์ธ ๋ชจ๋ ์ด๋ฒคํธ์์ ์๋ํ๋ ๊ฒ์ด ์๋๋ผ ํด๋น ์์๊ฐ drag
๊ฐ ๊ฐ๋ฅํ๋ค๋ ๊ฒ์ ๋จผ์ ์๋ ค์ค์ผ ํ๋ค. ํค์๋๋ draggable
๋ก, drag
๋ฅผ ์ํ๋ ์์์ ์์ฑํด์ฃผ๋ฉด ๋๋ค.
1
2
<div draggable="true">๋๋๊ทธ ๊ฐ๋ฅ</div>
<div>๋๋๊ทธ ๋ถ๊ฐ๋ฅ</div>
โก๏ธdrag ์ด๋ฒคํธ ์ข ๋ฅ
DnD๋ฅผ ์๊ฐํด๋ณด๋ฉด drag ์ด๋ฒคํธ์๋ ๋ค์ํ ์ข ๋ฅ๊ฐ ์์ ์ ๋ฐ์ ์๋ค. ์๋ฅผ ๋ค์ด, drag๋ฅผ ์์ํ์ ๋, dropํ์ ๋ ๋ฑ! ์ฆ ๊ด๋ จ๋ ์ด๋ฒคํธ๋ ์ข ๋ฅ๊ฐ ์ฌ๋ฟ ์๋ค.
์ด๋ฒคํธ | ์ด๋ฒคํธ ํธ๋ค๋ฌ | ์ค๋ช |
---|---|---|
drag | ondrag | ์์๋ ํ ์คํธ ๋ธ๋ก์ ๋๋๊ทธ ํ ๋ ๋ฐ์ํ๋ค. |
dragend | ondragend | ๋๋๊ทธ๋ฅผ ๋๋์ ๋ ๋ฐ์ํ๋ค. (๋ง์ฐ์ค ๋ฒํผ์ ๋ผ๊ฑฐ๋ ESC ํค๋ฅผ ๋๋ฅผ ๋) |
dragenter | ondragenter | ๋๋๊ทธํ ์์๋ ํ ์คํธ ๋ธ๋ก์ ์ ํฉํ ๋๋กญ ๋์์์ ์ฌ๋ผ๊ฐ์ ๋ ๋ฐ์ํ๋ค. |
dragexit | ondragexit | ์์๊ฐ ๋ ์ด์ ๋๋๊ทธ์ ์ง์ ์ ์ธ ๋์์ด ์๋ ๋ ๋ฐ์ํ๋ค. |
dragleave | ondragleave | ๋๋๊ทธํ๋ ์์๋ ํ ์คํธ ๋ธ๋ก์ด ์ ํฉํ ๋๋กญ ๋์์์ ๋ฒ์ด๋ฌ์ ๋ ๋ฐ์ํ๋ค. |
dragover | ondragover | ์์๋ ํ ์คํธ ๋ธ๋ก์ ์ ํฉํ ๋๋กญ ๋์ ์๋ก ์ง๋๊ฐ ๋ ๋ฐ์ํ๋ค. (๋งค ์๋ฐฑ ๋ฐ๋ฆฌ์ด๋ง๋ค ๋ฐ์ํ๋ค.) |
dragstart | ondragstart | ์ฌ์ฉ์๊ฐ ์์๋ ํ ์คํธ ๋ธ๋ก์ ๋๋๊ทธํ๊ธฐ ์์ํ์ ๋ ๋ฐ์ํ๋ค. |
drop | ondrop | ์์๋ ํ ์คํธ ๋ธ๋ก์ ์ ํฉํ ๋๋กญ ๋์์ ๋๋กญํ์ ๋ ๋ฐ์ํ๋ค. |
์ด ์ค์์, DnD ๊ตฌํ์ ์ฌ์ฉํ ์ด๋ฒคํธ๋ ondragenter
, ondragstart
, ondragend
3๊ฐ์ง๋ค.
โ ๊ตฌํ ํ๋ฆ
๋๋ ํ์ฌ Next.js๋ฅผ ์ด์ฉํ๊ณ ์์ด์ ์๊ฐํ๋ ํ๋ฆ์ ๋ค์๊ณผ ๊ฐ๋ค.
- api๋ฅผ ๋ฐ์์จ๋ค.
- ์ฌ์ฉ์๊ฐ ์ ํํ ์คํํ ํฌ์ผ๋ชฌ์ ๋ฐฐ์ด ํํ๋ก ์ ์ญ ์ํ๊ด๋ฆฌํ๋ค.
- ์ฌ์ฉ์๊ฐ ๋๋๊ทธ ํ ๋ด์ฉ์ ๋ฐํ์ผ๋ก ์ํ๋ฅผ ์ ๋ฐ์ดํธ ํ๋ค.
- ํด๋น ๋ด์ฉ์ ๋ ๋๋ง ํ๋ค.
1๋ฒ๊ณผ 2๋ฒ ๋ด์ฉ์ ์๋ตํ๊ณ ๊ฐ๋จํ ์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ง๊ณ ์์ฑํ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ๋ค.
โก๏ธstate ์ ์
ํ์ํ state๋ฅผ ์ ์ํ๋ค. ์์ ๋ฐ์ดํฐ arr๊ณผ dragํ๋ item๊ณผ drop์๋ฆฌ์ ์๋ item์ state๋ฅผ ์ ์ํด์ฃผ์๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//๋ฐ์ดํฐ ๋ฐฐ์ด
let [arr, setArr] = useState<string[]>([
"ํผ์นด์ธ1",
"๋ผ์ด์ธ2",
"ํ์ด๋ฆฌ3",
"๊ผฌ๋ถ์ด4",
])
//dragํ๋ item
let [dragItem, setDragItem] = useState<HTMLDivElement | null>(null)
//dropํ๋ ์์น์ ์๋ item
let [destinationItem, setDestinationItem] = useState<HTMLDivElement | null>(
null
)
โก๏ธ์ด๋ฒคํธ ์ ์
์ด๋ฒคํธ ์ข ๋ฅ๋ 3๊ฐ์ง๋ฅผ ์ฌ์ฉํ๋ค.
- ํ์ฌ drag ํ๊ณ ์๋ ์์ดํ ์ ์ฐพ๊ธฐ
- drop๋๋ ์์น ์์ดํ ์ฐพ๊ธฐ
- drop ์ดํ arr ์ ๋ฐ์ดํธ ํ๊ธฐ
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
//ํ์ฌ ๋๋๊ทธ ํ๋ ์์ดํ
์ ๋ฌด์์ธ๊ฐ?
const dragStart = (e: React.DragEvent<HTMLDivElement>) => {
const targetItem = e.target as HTMLDivElement
setDragItem(targetItem)
}
//์ด๋ค ์์ดํ
์๋ก ๋๋๊ทธ ๋๋๊ฐ?
const dragDestination = (e: React.DragEvent<HTMLDivElement>) => {
const targetItem = e.target as HTMLDivElement
setDestinationItem(targetItem)
}
//dropํ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ ๋นํ๋ค.
const dropNow = () => {
// drop ์๋ฆฌ์ ์ธ๋ฑ์ค ์ฐพ๊ธฐ
const destinationIndex = arr.findIndex(
(item) => destinationItem?.innerText === item
)
// drag ์๋ฆฌ์ ์ธ๋ฑ์ค ์ฐพ๊ธฐ
const dragIndex = arr.findIndex((item) => dragItem?.innerText === item)
// arr ์์ ๋ฐ๊พธ๊ธฐ
if (
dragIndex !== -1 &&
destinationIndex !== -1 &&
//dragํ๋ ์ธ๋ฑ์ค์ destination ์ธ๋ฑ์ค๊ฐ ๊ฐ์ผ๋ฉด ๊ตณ์ด ์คํํ ํ์๊ฐ ์๋ค.
dragIndex !== destinationIndex
) {
let newArr = [...arr]
//drag ํ๋ item์ ๊บผ๋ด๊ณ , newArr์์ ํด๋น item์ ์ญ์ ํจ
const [draggedItem] = newArr.splice(dragIndex, 1)
//๊บผ๋ธ ์์ดํ
์ ์ ์ ํ ์์น์ ๋ฃ์
newArr.splice(destinationIndex, 0, draggedItem)
setArr(newArr)
}
}
โก๏ธ๋ ๋๋ง
์์์ ์์ฑํ ๊ฒ๊ณผ ๊ฐ์ด draggable
ํ์์ ์ ์ํด ๋ ํจ์๋ฅผ ๋ฃ์ด์ฃผ๋ฉด ๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//์๋ต
{
arr.map((item, i) => {
return (
<div
key={i}
className={`bg-red-100 box-${i + 1} h-[300px]`}
draggable
onDragEnter={(e) => {
dragDestination(e)
}}
onDragStart={(e) => {
dragStart(e)
}}
onDragEnd={() => {
dropNow()
}}
>
{item}
</div>
)
})
}