드래그 앤 드랍(2): MouseEvent, TouchEvent, PointerEvent 학습과 구현
들어가기
웹에서 드래그 앤 드롭(Drag and Drop) 기능은 사용자의 직관적인 인터랙션을 제공하기 위해 매우 유용한 UI 요소이다. HTML5의 Drag and Drop API를 사용하면 간단하게 구현할 수 있지만, 이 방식은 모바일 환경에서 작동하지 않는다는 치명적인 단점이 존재한다. 따라서 보다 넓은 플랫폼 호환성과 안정성을 확보하려면, MouseEvent, TouchEvent, PointerEvent를 직접 다루는 방식으로 구현하는 것이 좋다.
각 이벤트가 어떤 상황에서 발생하며, 어떤 속성과 동작을 가지는지에 대한 이해를 한 후 직접 구현해보자
참고로 이 이벤트 객체는 모두 UIEvent와 Event를 상속하고 있기 때문에, 더 구체적인 속성이나 메서스가 궁금하다면 글 맨 아래에 첨부한 참고 자료를 확인하면 도움이 될 것 같다.
MouseEvent - 데스크탑의 기본 입력 이벤트
MouseEvent는 사용자가 포인팅 장치(마우스 등)를 사용해 상호작용할 때 발생하는 이벤트를 나타낸다.
주요 속성
button
클릭한 마우스 버튼 번호 (0: 좌, 1: 중, 2: 우)
buttons
눌린 마우스 버튼들의 상태 (비트마스크)
altKey
Alt 키가 눌렸는지 여부 (true / false)
ctrlKey
Ctrl 키가 눌렸는지 여부
metaKey
Meta 키(Mac 기준 Command)가 눌렸는지 여부
shiftKey
Shift 키가 눌렸는지 여부
pageX, pageY
전체 문서 기준 마우스 좌표 (스크롤 고려됨)
screenX, screenY
화면 기준 마우스 포인터 좌표
clientX, clientY
뷰포트 기준 마우스 포인터의 좌표
x, y
clientX, clientY의 별칭
offsetX, offsetY
이벤트 대상 요소의 패딩 기준 내부 좌표
movementX, movementY
마지막 mousemove 이벤트 이후 포인터가 이동한 거리
relatedTarget
마우스가 방금 떠난 곳 또는 새로 들어갈 곳 (mouseover, mouseout 시 관련 요소)
주요 이벤트
click
마우스를 클릭했을 때 (mousedown + mouseup)
dblclick
마우스를 빠르게 두 번 클릭할 때 발생
contextmenu
마우스 오른쪽 클릭 시 발생
mousedown
마우스 버튼을 누를 때 발생
mouseup
마우스 버튼에서 손을 뗄 때 발생
mousemove
마우스를 움직일 때 발생
mouseover
요소 위로 마우스 포인터가 올라갈 때 발생 (버블링 O)
mouseout
요소에서 마우스 포인터가 벗어날 때 발생 (버블링 O)
mouseenter
요소 위로 마우스 포인터가 올라갈 때 발생 (버블링 X)
mouseleave
요소에서 마우스 포인터가 벗어날 때 발생 (버블링 X)
TouchEvent - 모바일 기기의 터치 입력 처리
TouchEvent는 스마트폰, 태블릿 등 터치 스크린 장치의 접촉 상태 변화를 감지하는 이벤트이다.
마우스 이벤트와 다르게 여러 접촉점을 동시에 처리할 수 있다.
주요 속성
altKey
이벤트 발생 시 Alt 키가 눌려 있었는지 여부 (Boolean)
ctrlKey
이벤트 발생 시 Ctrl 키가 눌려 있었는지 여부 (Boolean)
metaKey
이벤트 발생 시 Meta 키(⌘ 등)가 눌려 있었는지 여부 (Boolean)
shiftKey
이벤트 발생 시 Shift 키가 눌려 있었는지 여부 (Boolean)
touches
화면에 닿아 있는 모든 터치 지점을 담은 TouchList 객체
targetTouches
현재 이벤트 대상 요소에서 발생한 터치 지점들만 담은 TouchList 객체
changedTouches
직전 이벤트 이후 상태가 변경된 터치 지점만 담은 TouchList 객체
주요 이벤트
touchstart
하나 이상의 손가락이 화면에 닿았을 때 발생
touchmove
터치된 손가락이 움직였을 때 발생
touchend
손가락이 터치 표면에서 떨어졌을 때 발생
touchcancel
터치 이벤트가 시스템에 의해 취소되었을 때 발생(알림 창 등장 등 )
PointerEvent - 모든 입력 장치를 통합한 새로운 방식
PointerEvent 는 마우스, 터치, 펜 등 모든 포인팅 장치를 통합하여 처리할 수 있는 이벤트다.
크로스 플랫폼 대응이 필요할 경우, PointerEvent를 우선적으로 고려하는 것이 좋다.
주요 속성
pointerId
포인터를 구별하기 위한 고유 ID. 멀티터치 등에서 포인터를 식별할 수 있음.
pointerType
"mouse", "touch", "pen" 중 하나로 포인터 종류를 나타냄.
isPrimary
해당 포인터가 기본 입력인지 여부 (true/false).
width, height
입력 지점(손가락, 펜 등)의 접촉 영역 너비와 높이 (픽셀 단위).
pressure
압력 값 (0 ~ 1 사이의 값, 마우스는 0 또는 0.5/1 등).
tiltX, tiltY
입력 장치의 기울기. -90 ~ 90 사이 값으로 주로 펜에서 사용됨.
twist
포인터의 회전 정도 (0~359도).
tangentialPressure
포인터의 측면 압력 (펜에서 사용, 일부 장치만 지원).
clientX, clientY
뷰포트를 기준으로 한 포인터 위치 (좌표).
pageX, pageY
문서 전체 기준의 포인터 위치 (스크롤 고려됨).
screenX, screenY
화면 전체 기준의 포인터 위치.
buttons
눌려진 버튼 상태를 비트마스크로 표현.
altKey, ctrlKey, metaKey, shiftKey
조합 키의 눌림 여부를 나타냄.
주요 이벤트
pointerdown
포인터(마우스, 터치, 펜 등)가 눌렸을 때 발생
pointerup
포인터에서 손을 뗐을 때 발생
pointermove
포인터가 움직일 때 발생
pointercancel
시스템이 포인터 이벤트를 취소했을 때 발생
pointerover
포인터가 요소에 올라갈 때 발생 (버블링 O)
pointerout
포인터가 요소에서 벗어날 때 발생 (버블링 O)
pointerenter
포인터가 요소에 들어왔을 때 발생 (버블링 X)
pointerleave
포인터가 요소를 벗어났을 때 발생 (버블링 X)
gotpointercapture
포인터 캡처가 설정되었을 때 발생
lostpointercapture
포인터 캡처가 해제되었을 때 발생
관련 메서드
setPointerCapture()
포인터 이벤트를 요소가 계속 수신하도록 설정합니다.
포인터가 요소 밖으로 나가도 이벤트를 계속 받기 위함
releasePointerCapture()
캡처한 포인터 이벤트 수신 권한을 해제합니다.
드래그 또는 인터랙션 종료 시 캡처 해제
hasPointerCapture()
해당 요소가 특정 포인터 ID의 이벤트를 캡처 중인지 확인
포인터 캡처 여부 확인 또는 조건 분기 처리
navigator.maxTouchPoints
디바이스가 동시에 감지할 수 있는 터치 포인터 수
터치 디바이스인지 여부를 판단할 때 사용
PointerEvent로 드래그앤 드랍 구현하기
PointerEvent는 MouseEvent와 ToucheEvent를 통합한 것이기 때문에 PointerEvent를 사용해서 브라우저, 모바일 둘 다 대응되는 드래그앤 드랍을 만들려고 한다. 먼저 프로젝트에 직접 적용하기 전 Drag And Drop API를 학습할 때 만들어본 간단한 예시를 PointerEvent 기반으로 다시 구현해보자
요소 옮기기

요소 위치 바꾸기(서로 바꾸기)

요소 위치 바꾸기(사이에 끼우기)

잔상 띄우기
Pointer 이벤트 기반 드래그 구현에서는 기본 Drag API처럼 자동으로 잔상이 생성되지 않는다. 따라서 사용자에게 시각적 피드백을 제공하려면, 드래그 중인 아이템을 따라다니는 잔상(고스트) 요소를 직접 만들어야 한다.
아래와 같은 로직으로 구현 가능할 듯 하다:
요소를 누르면 (
pointerdown)드래그할 아이템의 ID와 시작 좌표를 저장한다.
마우스를 움직이면 (
pointermove)처음 누른 지점에서 일정 거리 이상 움직였을 때만 드래그로 간주하여 드래그 잔상(ghost 요소)을 생성한다.
드래그 중이라면 마우스 위치에 따라 잔상을 이동시킨다.
마우스를 놓으면 (
pointerup)드래그 잔상을 없앤다.
1. 요소를 누르면 (pointerdown) 드래그할 아이템의 ID와 시작 좌표를 저장
pointerdown) 드래그할 아이템의 ID와 시작 좌표를 저장2. 마우스를 움직이면 (pointermove) 드래그일 때만 잔상 생성/이동
pointermove) 드래그일 때만 잔상 생성/이동3. 놓으면 (pointerup) 초기화 하면서 잔상 제거
pointerup) 초기화 하면서 잔상 제거
영역 벗어나면 유지 안되는 문제 해결

그런데 위의 로직대로 구현하면 문제가 있다. 드래그 영역에서 벗어나게되면 위 gif처럼 드래그 요소가 유지 되지 않는다는점이다. 이를 해결하려면 이벤트를 전역에 부착시켜야한다.
1. 전역에 이벤트 부착
2. 드래그 가능 영역이 아니면 드래그 불가능
드래그 중 커서가 컨테이너의 경계를 벗어나면 삽입 불가능하기 때문에insertionIndex를 null로 설정한다.
3. 드래그된 아이템이 삽입될 위치(인덱스) 계산
마우스가 컨테이너 안에 있을 경우에는, 아이템들의 위치를 계산하여 어느 위치에 삽입할지 결정한다.
마우스 커서의 Y 좌표가 각 아이템의 세로 중앙(중간점)보다 위에 있는 경우, 해당 아이템 앞에 삽입선을 표시한다. 조건을 만족하는 가장 첫 번째 아이템 앞 위치를 삽입될 위치로 설정하며, 그렇지 않으면 마지막을 삽입 위치로 설정한다.

여기까지 완성된 전체 코드는 아래와 같다.
참고 자료
https://developer.mozilla.org/ko/docs/Web/API/MouseEvent
https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent
https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent
Last updated