기본적인 이벤트
<div
onMouseDown={startLongPress}
onMouseUp={removeAndReset}
onMouseLeave={removeAndReset}
onTouchStart={startLongPress}
onTouchEnd={removeAndReset}
onContextMenu={(e) => e.preventDefault()}
>
const startLongPress = useCallback((cat: string, clientX: number, clientY: number) => {
if (longPressTimer.current) clearTimeout(longPressTimer.current);
if (cleanupRef.current) cleanupRef.current();
pressStartPos.current = { x: clientX, y: clientY };
const onMove = (e: MouseEvent | TouchEvent) => {
const p = e instanceof TouchEvent ? e.touches[0] : (e as MouseEvent);
/**
* 구체적 의미
draggingCatRef.current가 null이면
아직 900ms 길게 누른 후에 드래그가 활성화되지 않은 상태
이때 이동 거리가 8px 이상이면
removeAndReset()를 호출해서 긴 누르기 타이머를 취소
드래그 진입을 포기
왜 필요한가?
사용자가 길게 누르려다가 손이 조금 움직였을 때
또는 스크롤/슬라이드 의도일 때
그 동작을 “드래그 시작”으로 잘못 인식하지 않게 하기 위함
*/
if (!draggingCatRef.current) {
const dx = p.clientX - pressStartPos.current.x;
const dy = p.clientY - pressStartPos.current.y;
if (Math.sqrt(dx * dx + dy * dy) > 8) removeAndReset();
return;
}
if (e instanceof TouchEvent && e.cancelable) e.preventDefault();
setGhostPos({ x: p.clientX, y: p.clientY });
setOverDeleteZone(isOverZone(p.clientX, p.clientY));
};
const onEnd = (e: MouseEvent | TouchEvent) => {
const p = e instanceof TouchEvent
? (e as TouchEvent).changedTouches[0]
: (e as MouseEvent);
if (draggingCatRef.current && isOverZone(p.clientX, p.clientY)) {
const catToDelete = draggingCatRef.current;
onDelete(catToDelete);
onFilterChange('all');
}
removeAndReset();
};
const removeAndReset = () => {
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onEnd);
document.removeEventListener('touchmove', onMove);
document.removeEventListener('touchend', onEnd);
if (longPressTimer.current) {
clearTimeout(longPressTimer.current);
longPressTimer.current = null;
}
cleanupRef.current = null;
draggingCatRef.current = null;
setDraggingCat(null);
setOverDeleteZone(false);
};
cleanupRef.current = removeAndReset;
//900ms 동안 그대로 있으면 draggingCat를 설정해 실제 드래그 모드에 진입
longPressTimer.current = setTimeout(() => {
draggingCatRef.current = cat;
setDraggingCat(cat);
setGhostPos({ x: clientX, y: clientY });
}, 900);
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onEnd);
document.addEventListener('touchmove', onMove, { passive: false });
document.addEventListener('touchend', onEnd);
}, [onDelete, onFilterChange, isOverZone]);
const removeAndReset = () => {
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onEnd);
document.removeEventListener('touchmove', onMove);
document.removeEventListener('touchend', onEnd);
if (longPressTimer.current) {
clearTimeout(longPressTimer.current);
longPressTimer.current = null;
}
cleanupRef.current = null;
draggingCatRef.current = null;
setDraggingCat(null);
setOverDeleteZone(false);
};
| 함수 | 역할 |
| preventDefault() | 브라우저 기본 동작 막기
|
| stopPropagation() | 이벤트 전파 막기 |
// 버튼 밖으로 마우스가 나가도 드래그 유지하려면
// 특정 엘리먼트에만 걸면 마우스가 벗어나는 순간 드래그 끊김 ❌
element.addEventListener('mousemove', onMove); // 엘리먼트 밖 나가면 중단
document.addEventListener('mousemove', onMove); // 어디서든 드래그 유지 ✅
'WEB개발 > REACT' 카테고리의 다른 글
| unmount (0) | 2026.05.26 |
|---|---|
| vite-plugin-route-meta (0) | 2026.01.15 |
| pull to refresh (0) | 2026.01.12 |
| react router (navigate) (0) | 2025.12.17 |
| [hook] useCallback (0) | 2025.12.16 |