포인터 이벤트란
과거에는 오직 마우스 이벤트(mousedown, mouseup, mousemove 등)만 존재했습니다.
터치 디바이스가 보편화되면서 기존 마우스 이벤트로는 지원할 수 없는 멀티터치와 같은 새로운 입력 기능이 필요해졌고, 이를 위해 별도의 터치 이벤트(touchstart, touchend, touchmove 등)가 도입되었습니다. 그러나 터치와 마우스를 동시에 지원하려면 코드가 복잡해졌고, 스타일러스나 펜 입력과 같은 다른 장치의 특성을 고려하기엔 한계가 있었습니다. 이러한 문제를 해결하고자 Pointer Events 표준이 등장했습니다.
- 통합성: 마우스, 터치, 펜 등 다양한 입력 장치에 대해 동일한 이벤트 핸들러를 사용할 수 있습니다.
- 멀티터치 지원: 각 포인터에 고유한 pointerId를 부여하여 여러 터치 입력을 개별적으로 추적할 수 있습니다.
- 포인터 캡쳐: 특정 요소에 입력 포인터를 “고정”하여, 포인터가 문서의 다른 영역으로 이동하더라도 해당 요소가 계속해서 이벤트를 수신할 수 있습니다.
포인터 이벤트의 주요 이벤트
Pointer Event |
유사한 마우스 이벤트 |
pointerdown |
mousedown |
pointerup |
mouseup |
pointermove |
mousemove |
pointerover |
mouseover |
pointerout |
mouseout |
pointerenter |
mouseenter |
pointerleave |
mouseleave |
주요 속성
포인터 이벤트를 활용한 드래그 앤 드롭 구현
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>포인터 이벤트: 드래그와 상태</title>
<style>
#dragBox {
width: 80px; height: 80px; background: #42a5f5; color: white;
position: absolute; top: 50px; left: 50px; text-align: center; line-height: 80px;
touch-action: none; cursor: move;
}
#status { margin-top: 150px; }
</style>
</head>
<body>
<div id="dragBox">드래그</div>
<div id="status"></div>
<script>
const dragBox = document.getElementById('dragBox');
const status = document.getElementById('status');
dragBox.addEventListener('pointerdown', (e) => {
e.preventDefault();
dragBox.setPointerCapture(e.pointerId);
const rect = dragBox.getBoundingClientRect();
const shiftX = e.clientX - rect.left;
const shiftY = e.clientY - rect.top;
status.textContent = `포인터: ${e.pointerType}, ID: ${e.pointerId}`;
const moveBox = (moveEvent) => {
dragBox.style.left = `${moveEvent.clientX - shiftX}px`;
dragBox.style.top = `${moveEvent.clientY - shiftY}px`;
};
dragBox.addEventListener('pointermove', moveBox);
dragBox.addEventListener('pointerup', () => {
dragBox.removeEventListener('pointermove', moveBox);
status.textContent = '드래그 종료';
}, { once: true });
});
dragBox.ondragstart = () => false;
</script>
</body>
</html>
- setPointerCapture: 포인터 입력을 dragBox에 고정하여 요소 밖 이동에도 이벤트 수신
- pointerType: 마우스, 터치, 펜 여부 표시
- touch-action: none: 터치 기본 동작(스크롤, 확대/추소 등) 차단
포인터 캡처를 이용한 슬라이더 구현
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>포인터 이벤트: 슬라이더</title>
<style>
#track {
width: 400px; height: 20px; background: #e0e0e0; position: relative;
margin: 50px; border-radius: 10px;
}
#knob {
width: 30px; height: 30px; background: #ff5722; border-radius: 50%;
position: absolute; top: -5px; left: 0; touch-action: none; cursor: pointer;
}
#value { font-size: 18px; }
</style>
</head>
<body>
<div id="track">
<div id="knob"></div>
</div>
<div id="value">0%</div>
<script>
const knob = document.getElementById('knob');
const track = document.getElementById('track');
const value = document.getElementById('value');
knob.addEventListener('pointerdown', (e) => {
e.preventDefault();
knob.setPointerCapture(e.pointerId);
const trackRect = track.getBoundingClientRect();
const knobRect = knob.getBoundingClientRect();
const shiftX = e.clientX - knobRect.left;
const updateKnob = (moveEvent) => {
let newLeft = moveEvent.clientX - trackRect.left - shiftX;
newLeft = Math.max(0, Math.min(newLeft, trackRect.width - knobRect.width));
knob.style.left = `${newLeft}px`;
const percent = Math.round((newLeft / (trackRect.width - knobRect.width)) * 100);
value.textContent = `${percent}%`;
};
knob.addEventListener('pointermove', updateKnob);
knob.addEventListener('pointerup', () => {
knob.removeEventListener('pointermove', updateKnob);
}, { once: true });
});
</script>
</body>
</html>
- setPointerCapture: 트랙 밖으로 벗어나도 knob이 이벤트를 수신
- Math.max/min: 슬라이더 트랙 내 이동 범위 제한
포인터 이벤트 주의점
- 기본 동작 방지: pointercancel 이벤트 방지를 위한 touch-action: none과 ondragstart = () => false 설정