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.