Draggable object 리뷰

drag 처리에 대해 많은 고민이 묻어있는 포스팅을 발견하여 리뷰해 봅니다.

2024-02-12

소개

Interactive한 UX를 구현하다보면 drag 처리가 필요한 경우가 있습니다. Mouse와 Pointer의 up/down/move 이벤트를 받아 처리하면 어느 정도 원하는 구현이 나옵니다. 하지만 어딘지 모르게 어색한 혹은 예기치 못한 오동작이 발생하기도 하는데요. 마침 drag 처리에 대해 많은 고민이 묻어있는 포스팅을 발견하여 리뷰해 봅니다.

Draggable objects

Mouse 이벤트 사용하기

🖱️ Mouse events only

MouseOnlyXState

not_dragging
Dragme

마우스 이벤트만으로 dragging을 구현할 때 다음의 문제가 있습니다.

  • 마우스를 빠르게 움직이면 이벤트 처리를 하지 못합니다.
  • 포인터가 원 밖으로 나간 상태에서 마우스 up을 하면 dragging 상태에 머무르는 문제가 있습니다.

이를 보완하기 위해 아래 예시와 같이, element가 아닌 document 엘리먼트에 mousemove, mouseup 이벤트를 등록하여 처리할 수 있습니다.

not_dragging
Dragme

Touch 이벤트 사용하기

👆 Touch events

mousedown, mouseup, mousemove 대신 touchstart, touchend, touchmove 이벤트를 사용합니다. touchmove, touchendmousemove, mouseend 와 다르게 영역 밖에서도 이벤트를 전달합니다. 따라서 document에 등록하지 않아도 동일한 동작을 확인할 수 있습니다.

ByTouchEvent

not_dragging
Dragme

Pointer 이벤트 사용하기

🖱️👆 Pointer events

Pointer 이벤트는 mouse, touch 이벤트 모두를 처리합니다.

Pointer 이벤트의 pointerId를 엘리먼트의 setPointerCapture 함수로 넘겨주면 document에 이벤트를 등록하는 것과 동일한 기능을 사용할 수 있습니다.

ByPointerEvent

not_dragging
Dragme

Fix: capture the mouse

다음과 같은 동작에도 drag 동작이 멈추지 않고 지속하여야 합니다.

  • drag 상태에서 빠르게 흔들기
  • drag 영역 밖에서 다시 들어오기
  • drag 영역 밖에서 release 했을 때
  • drag 영역 밖에서 release 하고 다시 들어왔을 때
  • drag 중 alt+tab으로 윈도우 포커스 변경할 때

이를 위해 drag element의 setPointerCapture(event.pointerId)를 호출하여 drag 영역 밖에서도 이벤트 전달이 가능하도록 합니다.

Fix: scrolling with touch

모바일 환경에서는 한 손가락 drag로 스크롤을 합니다. 엘리먼트를 drag로 이동할 경우 스크롤과 drag 동작이 동시에 이루어 집니다. 이를 막기 위해 drag 영역에 touch-action: none 스타일을 적용합니다. 이 경우 모든 drag 영역에서 스크롤이 불가능 합니다.

drag를 적용하는 엘리먼트의 touchstart 이벤트를 preventDefault() 해 주면 엘리먼트만 drag 동작을 하면서 스크롤이 되지 않으며 동시에 drag 영역에서 스크롤이 가능해 집니다.

Feature: handle drag offset

이벤트 위치를 기준으로 엘리먼트를 잡을경우 drag 시작 시, drag element의 중심이 순간적으로 mouse pointer로 이동합니다.

이 동작을 보완하기 위해, drag element의 중심과 event 위치와의 차이를 저장하고 그 차이만큼 move 동작시에 보완해 줍니다.

Fix: context menu

Context 메뉴는 운영체제마다 호출 방식이 다릅니다. pointerdown 이벤트에 따른 pointerup 이벤트를 항상 전달해 주지 않습니다.

이를 보완하기 위해 left button에서만 동작하도록 수정합니다.

🖱️ Variant: draggable text/images

drag element 안에 text, image 등이 있는 경우에 확인해야 할 사항입니다.

Fix: text selection

drag element를 이동 시 때떄로 텍스트가 선택 됩니다. user-select: none을 drag element에 설정하면 select all 동작에서 조차 선택을 할 수 없습니다.

이를 위해 dragging 중에만 user-select: none 설정을 합니다.

Fix: text and image drag

drag 영역 밖에서 부터 drag element 내의 text 까지 text selection을 한 뒤, text 선택 영역에서 drag를 하면 텍스트 전체를 copy & paste 할 수 있습니다.

drag element 내의 text selection 영역에서 drag 동작을 우선시하려면, 해당 엘리먼트의 dragstart 이벤트를 preventDefault()해 줍니다.

다음은 위에서 설명한 권장사항을 적용한 예시입니다.

not_dragging
Dragme

마치며

저자는 오랬동안 자신이 경험해 온 내용을 위 블로그에 정리 했습니다. 간단한 drag 기능 구현을 위해 고민할 경우 마우스 이벤트 처리에 머물러 있기마련 입니다. 반면 touch, pointer, drag 이벤트 등을 다루고 이들의 edge 케이스를 살펴 보면서 수년에 걸친 경험과 인사이트를 얻을 수 있었습니다.

블로그 원본에서는 더욱 다양하고 흥미로운 tip과 trick을 제공하니 관심있으신 분들은 본문의 설명과 함께 예시링크도 함께 확인해 보시면 좋겠습니다.

Loading script...