소개
Interactive한 UX를 구현하다보면 drag 처리가 필요한 경우가 있습니다. Mouse와 Pointer의 up/down/move 이벤트를 받아 처리하면 어느 정도 원하는 구현이 나옵니다. 하지만 어딘지 모르게 어색한 혹은 예기치 못한 오동작이 발생하기도 하는데요. 마침 drag 처리에 대해 많은 고민이 묻어있는 포스팅을 발견하여 리뷰해 봅니다.
Mouse 이벤트 사용하기
마우스 이벤트만으로 dragging을 구현할 때 다음의 문제가 있습니다.
- 마우스를 빠르게 움직이면 이벤트 처리를 하지 못합니다.
- 포인터가 원 밖으로 나간 상태에서 마우스 up을 하면
dragging
상태에 머무르는 문제가 있습니다.
이를 보완하기 위해 아래 예시와 같이, element가 아닌 document
엘리먼트에 mousemove
, mouseup
이벤트를 등록하여 처리할 수 있습니다.
Touch 이벤트 사용하기
mousedown
, mouseup
, mousemove
대신 touchstart
, touchend
, touchmove
이벤트를 사용합니다.
touchmove
, touchend
는 mousemove
, mouseend
와 다르게 영역 밖에서도 이벤트를 전달합니다.
따라서 document
에 등록하지 않아도 동일한 동작을 확인할 수 있습니다.
Pointer 이벤트 사용하기
Pointer 이벤트는 mouse, touch 이벤트 모두를 처리합니다.
Pointer 이벤트의 pointerId
를 엘리먼트의 setPointerCapture
함수로 넘겨주면 document
에 이벤트를 등록하는 것과 동일한 기능을 사용할 수 있습니다.
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()
해 줍니다.
다음은 위에서 설명한 권장사항을 적용한 예시입니다.
마치며
저자는 오랬동안 자신이 경험해 온 내용을 위 블로그에 정리 했습니다. 간단한 drag 기능 구현을 위해 고민할 경우 마우스 이벤트 처리에 머물러 있기마련 입니다. 반면 touch, pointer, drag 이벤트 등을 다루고 이들의 edge 케이스를 살펴 보면서 수년에 걸친 경험과 인사이트를 얻을 수 있었습니다.
블로그 원본에서는 더욱 다양하고 흥미로운 tip과 trick을 제공하니 관심있으신 분들은 본문의 설명과 함께 예시링크도 함께 확인해 보시면 좋겠습니다.