Post

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ํ–ˆ์„ ๋•Œ ๋“ฑ! ์ฆ‰ ๊ด€๋ จ๋œ ์ด๋ฒคํŠธ๋„ ์ข…๋ฅ˜๊ฐ€ ์—ฌ๋Ÿฟ ์žˆ๋‹ค.

์ด๋ฒคํŠธ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์„ค๋ช…
dragondrag์š”์†Œ๋‚˜ ํ…์ŠคํŠธ ๋ธ”๋ก์„ ๋“œ๋ž˜๊ทธ ํ•  ๋•Œ ๋ฐœ์ƒํ•œ๋‹ค.
dragendondragend๋“œ๋ž˜๊ทธ๋ฅผ ๋๋ƒˆ์„ ๋•Œ ๋ฐœ์ƒํ•œ๋‹ค. (๋งˆ์šฐ์Šค ๋ฒ„ํŠผ์„ ๋–ผ๊ฑฐ๋‚˜ ESC ํ‚ค๋ฅผ ๋ˆ„๋ฅผ ๋•Œ)
dragenterondragenter๋“œ๋ž˜๊ทธํ•œ ์š”์†Œ๋‚˜ ํ…์ŠคํŠธ ๋ธ”๋ก์„ ์ ํ•ฉํ•œ ๋“œ๋กญ ๋Œ€์ƒ์œ„์— ์˜ฌ๋ผ๊ฐ”์„ ๋•Œ ๋ฐœ์ƒํ•œ๋‹ค.
dragexitondragexit์š”์†Œ๊ฐ€ ๋” ์ด์ƒ ๋“œ๋ž˜๊ทธ์˜ ์ง์ ‘์ ์ธ ๋Œ€์ƒ์ด ์•„๋‹ ๋•Œ ๋ฐœ์ƒํ•œ๋‹ค.
dragleaveondragleave๋“œ๋ž˜๊ทธํ•˜๋Š” ์š”์†Œ๋‚˜ ํ…์ŠคํŠธ ๋ธ”๋ก์ด ์ ํ•ฉํ•œ ๋“œ๋กญ ๋Œ€์ƒ์—์„œ ๋ฒ—์–ด๋‚ฌ์„ ๋•Œ ๋ฐœ์ƒํ•œ๋‹ค.
dragoverondragover์š”์†Œ๋‚˜ ํ…์ŠคํŠธ ๋ธ”๋ก์„ ์ ํ•ฉํ•œ ๋“œ๋กญ ๋Œ€์ƒ ์œ„๋กœ ์ง€๋‚˜๊ฐˆ ๋•Œ ๋ฐœ์ƒํ•œ๋‹ค. (๋งค ์ˆ˜๋ฐฑ ๋ฐ€๋ฆฌ์ดˆ๋งˆ๋‹ค ๋ฐœ์ƒํ•œ๋‹ค.)
dragstartondragstart์‚ฌ์šฉ์ž๊ฐ€ ์š”์†Œ๋‚˜ ํ…์ŠคํŠธ ๋ธ”๋ก์„ ๋“œ๋ž˜๊ทธํ•˜๊ธฐ ์‹œ์ž‘ํ–ˆ์„ ๋•Œ ๋ฐœ์ƒํ•œ๋‹ค.
dropondrop์š”์†Œ๋‚˜ ํ…์ŠคํŠธ ๋ธ”๋ก์„ ์ ํ•ฉํ•œ ๋“œ๋กญ ๋Œ€์ƒ์— ๋“œ๋กญํ–ˆ์„ ๋•Œ ๋ฐœ์ƒํ•œ๋‹ค.

์ด ์ค‘์—์„œ, DnD ๊ตฌํ˜„์— ์‚ฌ์šฉํ•œ ์ด๋ฒคํŠธ๋Š” ondragenter, ondragstart, ondragend 3๊ฐ€์ง€๋‹ค.

โœ…๊ตฌํ˜„ ํ๋ฆ„

๋‚˜๋Š” ํ˜„์žฌ Next.js๋ฅผ ์ด์šฉํ•˜๊ณ  ์žˆ์–ด์„œ ์ƒ๊ฐํ–ˆ๋˜ ํ๋ฆ„์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  1. api๋ฅผ ๋ฐ›์•„์˜จ๋‹ค.
  2. ์‚ฌ์šฉ์ž๊ฐ€ ์„ ํƒํ•œ ์Šคํƒ€ํŒ… ํฌ์ผ“๋ชฌ์„ ๋ฐฐ์—ด ํ˜•ํƒœ๋กœ ์ „์—ญ ์ƒํƒœ๊ด€๋ฆฌํ•œ๋‹ค.
  3. ์‚ฌ์šฉ์ž๊ฐ€ ๋“œ๋ž˜๊ทธ ํ•œ ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธ ํ•œ๋‹ค.
  4. ํ•ด๋‹น ๋‚ด์šฉ์„ ๋ Œ๋”๋ง ํ•œ๋‹ค.

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๊ฐ€์ง€๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

  1. ํ˜„์žฌ drag ํ•˜๊ณ  ์žˆ๋Š” ์•„์ดํ…œ์„ ์ฐพ๊ธฐ
  2. drop๋˜๋Š” ์œ„์น˜ ์•„์ดํ…œ ์ฐพ๊ธฐ
  3. 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>
    )
  })
}

๐Ÿ—‚๏ธ์ฐธ๊ณ  ์‚ฌ์ดํŠธ

This post is licensed under CC BY 4.0 by the author.