드래그 앤 드랍(2): MouseEvent, TouchEvent, PointerEvent 학습과 구현

들어가기

웹에서 드래그 앤 드롭(Drag and Drop) 기능은 사용자의 직관적인 인터랙션을 제공하기 위해 매우 유용한 UI 요소이다. HTML5의 Drag and Drop API를 사용하면 간단하게 구현할 수 있지만, 이 방식은 모바일 환경에서 작동하지 않는다는 치명적인 단점이 존재한다. 따라서 보다 넓은 플랫폼 호환성과 안정성을 확보하려면, MouseEvent, TouchEvent, PointerEvent를 직접 다루는 방식으로 구현하는 것이 좋다.

각 이벤트가 어떤 상황에서 발생하며, 어떤 속성과 동작을 가지는지에 대한 이해를 한 후 직접 구현해보자

참고로 이 이벤트 객체는 모두 UIEventEvent를 상속하고 있기 때문에, 더 구체적인 속성이나 메서스가 궁금하다면 글 맨 아래에 첨부한 참고 자료를 확인하면 도움이 될 것 같다.

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

포인터 캡처가 해제되었을 때 발생

관련 메서드

API
설명
사용 목적

setPointerCapture()

포인터 이벤트를 요소가 계속 수신하도록 설정합니다.

포인터가 요소 밖으로 나가도 이벤트를 계속 받기 위함

releasePointerCapture()

캡처한 포인터 이벤트 수신 권한을 해제합니다.

드래그 또는 인터랙션 종료 시 캡처 해제

hasPointerCapture()

해당 요소가 특정 포인터 ID의 이벤트를 캡처 중인지 확인

포인터 캡처 여부 확인 또는 조건 분기 처리

navigator.maxTouchPoints

디바이스가 동시에 감지할 수 있는 터치 포인터 수

터치 디바이스인지 여부를 판단할 때 사용

PointerEvent로 드래그앤 드랍 구현하기

PointerEvent는 MouseEvent와 ToucheEvent를 통합한 것이기 때문에 PointerEvent를 사용해서 브라우저, 모바일 둘 다 대응되는 드래그앤 드랍을 만들려고 한다. 먼저 프로젝트에 직접 적용하기 전 Drag And Drop API를 학습할 때 만들어본 간단한 예시를 PointerEvent 기반으로 다시 구현해보자

요소 옮기기

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

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

잔상 띄우기

Pointer 이벤트 기반 드래그 구현에서는 기본 Drag API처럼 자동으로 잔상이 생성되지 않는다. 따라서 사용자에게 시각적 피드백을 제공하려면, 드래그 중인 아이템을 따라다니는 잔상(고스트) 요소를 직접 만들어야 한다.

아래와 같은 로직으로 구현 가능할 듯 하다:

  1. 요소를 누르면 (pointerdown)

    • 드래그할 아이템의 ID와 시작 좌표를 저장한다.

  2. 마우스를 움직이면 (pointermove)

    • 처음 누른 지점에서 일정 거리 이상 움직였을 때만 드래그로 간주하여 드래그 잔상(ghost 요소)을 생성한다.

    • 드래그 중이라면 마우스 위치에 따라 잔상을 이동시킨다.

  3. 마우스를 놓으면 (pointerup)

    • 드래그 잔상을 없앤다.

1. 요소를 누르면 (pointerdown) 드래그할 아이템의 ID와 시작 좌표를 저장

2. 마우스를 움직이면 (pointermove) 드래그일 때만 잔상 생성/이동

3. 놓으면 (pointerup) 초기화 하면서 잔상 제거

영역 벗어나면 유지 안되는 문제 해결

그런데 위의 로직대로 구현하면 문제가 있다. 드래그 영역에서 벗어나게되면 위 gif처럼 드래그 요소가 유지 되지 않는다는점이다. 이를 해결하려면 이벤트를 전역에 부착시켜야한다.

1. 전역에 이벤트 부착

2. 드래그 가능 영역이 아니면 드래그 불가능

드래그 중 커서가 컨테이너의 경계를 벗어나면 삽입 불가능하기 때문에insertionIndexnull로 설정한다.

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