이전 포스트에서 만든 유틸함수에 큰 문제점이 있다.
바로 모바일 기기에서 동작하지 않다는 것이다.
힘들게 개발한 것이 모바일에서 동작하지 않다니 ㅠ퓨ㅜㅠ
TL;DR
모바일 기기에서는
MouseEvent
대신TouchEvent
를 사용한다.
window.matchMedia('(hover: none) and (pointer: coarse)').matches
를 통해 모바일 기기인지 여부를 파악한다.
기기에 따라mousedown
혹은touchdown
이벤트를 등록해준다.
사전 지식 — Touch Event
모바일 기기에서는 MouseEvent 대신 TouchEvent가 발생된다.
따라서 MouseEvent로 등록되었던 이벤트가 실행되지 않았던 것이다.
다행히 TouchEvent에서도 우리가 원하는 동작을 바로 찾을 수 있다.
mousedown
— touchdown
mousemove
— touchmove
mouseup
— touchend
TouchEvent의 속성 중
touches
— 모든 접촉점의 터치 리스트
targetTouches
— 현재 이벤트 타겟에서 시작된 터치 리스트
changedTouches
— 이전 이벤트에 할당된 모든 접촉점의 터치 리스트
터치 스크린 특성상 여러 터치 이벤트가 동시 실행될 수 있어서 터치 리스트를 반환하는 것 같다.
일반적으로 첫 Touch 이벤트를 사용하면 될 것이다.
정확히 차이에 대해 와닿진 않지만
움직일 때는 touches[0]
, 손을 땠을 때는 changedTouches[0]
를 사용하도록 하자.
기본 원리 — touch 이벤트 등록
이전 mouse 등록 절차와 같으니 자세한 설명을 생략하겠다.
<Boundary
onTouchStart={(touchEvent) => {
const touchMoveHandler = (moveEvent: TouchEvent) => {
setPosition({
x: moveEvent.touches[0].pageX - touchEvent.touches[0].pageX,
y: moveEvent.touches[0].pageY - touchEvent.touches[0].pageY,
});
};
const touchEndHandler = () => {
document.removeEventListener('touchmove', touchMoveHandler);
};
document.addEventListener('touchmove', touchMoveHandler);
document.addEventListener('touchend', touchEndHandler, { once: true });
}}
/>
onMouseStart
, onTouchStart
둘다 등록하긴 너무나 귀찮다...
MouseEvent, TouchEvent을 동시 등록할 수 있는 유틸을 만들어 보자.
그럼 브라우저 환경에 따라 MouseEvent를 등록할지 TouchEvent를 등록할지 판별할 수 있어야 한다.
심화 적용 — pc와 mobile를 구분
일반적으로 디바이스 크기로 pc와 mobile을 구분하지만
pc에서 작은 화면으로 볼 수도 있고 13인치 iPad를 사용할 수도 있다.
이를 해결해주는 아주 잘 정리된 블로그가 있다.
hover, pointer 쿼리를 이용하면 모바일 기기를 구분할 수 있다.
출처 https://paperblock.tistory.com/164
그렇다면 이 CSS Media Queries를 어떻게 사용할 수 있을까?
바로 window api, matchMedia를 사용하는 것이다.
아래 코드로 “현재 화면이 미디어쿼리의 범위에 들어가는지” 확인할 수 있다.
window.matchMedia('(max-width: 600px)').matches; // boolean
여러 쿼리를 같이 확인하고 싶으면 and
를 붙이면 된다.
window.matchMedia('(hover: none) and (pointer: coarse)').matches;
NextJS에서는 기본적으로 SSR하기 때문에 window가 undefined
할 수 있다.
따라서 아래와 같이 코드를 작성해주면 사용자의 화면이 터치 스크린인지 확인할 수 있다.
export const isTouchScreen =
typeof window !== 'undefined' && window.matchMedia('(hover: none) and (pointer: coarse)').matches;
심화 응용 — 최종 코드
utils/registDragEvent.ts
아직 부족한 부분이 많지만 상황에 맞춰 잘 수정하면 될 것이다.
const isTouchScreen =
typeof window !== 'undefined' && window.matchMedia('(hover: none) and (pointer: coarse)').matches;
export default function registDragEvent({
onDragChange,
onDragEnd,
stopPropagation,
}: {
onDragChange?: (deltaX: number, deltaY: number) => void;
onDragEnd?: (deltaX: number, deltaY: number) => void;
stopPropagation?: boolean;
}) {
if (isTouchScreen) {
return {
onTouchStart: (touchEvent: React.TouchEvent<HTMLDivElement>) => {
if (stopPropagation) touchEvent.stopPropagation();
const touchMoveHandler = (moveEvent: TouchEvent) => {
if (moveEvent.cancelable) moveEvent.preventDefault();
const deltaX = moveEvent.touches[0].pageX - touchEvent.touches[0].pageX;
const deltaY = moveEvent.touches[0].pageY - touchEvent.touches[0].pageY;
onDragChange?.(deltaX, deltaY);
};
const touchEndHandler = (moveEvent: TouchEvent) => {
const deltaX = moveEvent.changedTouches[0].pageX - touchEvent.changedTouches[0].pageX;
const deltaY = moveEvent.changedTouches[0].pageY - touchEvent.changedTouches[0].pageY;
onDragEnd?.(deltaX, deltaY);
document.removeEventListener('touchmove', touchMoveHandler);
};
document.addEventListener('touchmove', touchMoveHandler, { passive: false });
document.addEventListener('touchend', touchEndHandler, { once: true });
},
};
}
return {
onMouseDown: (clickEvent: React.MouseEvent<Element, MouseEvent>) => {
if (stopPropagation) clickEvent.stopPropagation();
const mouseMoveHandler = (moveEvent: MouseEvent) => {
const deltaX = moveEvent.pageX - clickEvent.pageX;
const deltaY = moveEvent.pageY - clickEvent.pageY;
onDragChange?.(deltaX, deltaY);
};
const mouseUpHandler = (moveEvent: MouseEvent) => {
const deltaX = moveEvent.pageX - clickEvent.pageX;
const deltaY = moveEvent.pageY - clickEvent.pageY;
onDragEnd?.(deltaX, deltaY);
document.removeEventListener('mousemove', mouseMoveHandler);
};
document.addEventListener('mousemove', mouseMoveHandler);
document.addEventListener('mouseup', mouseUpHandler, { once: true });
},
};
}
22.10.10 추가
모바일 기기에서 touch를 통해서 scroll를 내리게 된다.
따라서 drag하면서 scroll이 되는 버그가 발생하게 된다...
이를 해결해주기 위해
const touchMoveHandler = (moveEvent: TouchEvent) => {
if (moveEvent.cancelable) moveEvent.preventDefault();
};
document.addEventListener('touchmove', touchMoveHandler, { passive: false });
실제 동작은 아래 링크에서 볼 수 있습니다.
https://dnd-playground.vercel.app/
style 정보, 전체 코드는 아래 깃허브에서 살펴보면 됩니다.
https://github.com/bepyan/dnd-playground/
DND 마스터리