드래그는 상태가 아니라 결과다: 클릭과 드래그를 구분하지 못했을 때 생기는 문제
2026년 1월 27일
offset 문제를 해결했지만, 이번에는 클릭이 드래그로 인식되는 문제가 남았습니다.
이 글은 floating button 인터랙션을 개선하며 깨달은 “드래그는 상태가 아니라 결과다”라는 관점과, threshold·이벤트 순서·상태 분리를 통해 클릭과 드래그의 의도를 구분한 과정을 정리한 기록을 공유해 봅니다.
문제 상황
floating button은
- 클릭하면 사이드 패널을 열고
- 드래그하면 화면 상하로 이동해야 한다.
offset 문제를 이해하고 적용한 뒤, 클릭 직후의 요소가 튀는 문제는 해결됐지만 새로운 문제가 드러났다.
- 단순 클릭도 드래그로 인식됨
- click / mouseup / mousedown 이벤트 순서 때문에 isDragging 조건이 실제로 의미가 없어짐
- 드래그 중 텍스트 선택, 기본 브라우저 동작이 발생함
즉, “위치 계산(offset)은 맞아졌지만, 인터랙션의 의도(클릭 vs 드래그)는 구분되지 않는 상태”였다.
내가 처음 한 생각
처음에는 이렇게 생각했다. “드래그 중일 때만 onClick을 막으면 되지 않을까?”
그래서 if (!isDragging) 같은 조건을 추가했다.
onClick={() => {
if (!isDragging) {
setIsOpen(!isOpen);
}
}}
하지만 이 판단에는 두 가지 착각이 있었다.
- 드래그 여부는 boolean 하나로 충분하다고 생각함
- 이벤트가 직관적인 순서로 동작할 거라고 가정함
실제로는
- mouseup → click 순서 때문에
- isDragging은 click 시점에는 이미 false가 되었고
- 조건문은 논리적으로 항상 무력했다.
실제로 해 본 선택
접근을 완전히 바꿨다. 핵심 판단으로는 “드래그는 상태가 아니라 결과다.”라는 사고였다.
그래서 다음 선택을 했다.
- mousedown에서 즉시 드래그 시작하지 않음
- 시작 좌표(startY)만 저장
- threshold 도입
- 5px 이상 이동했을 때만 “드래그가 발생했다”고 판단
- 의도 판단용 상태 분리
- isDragging: 현재 드래그 중인가 (UI 상태)
- hasDragged: 실제로 드래그가 발생했는가 (의도 판단)
- 이벤트 순서 문제 해결
- mouseup에서 hasDragged를 바로 리셋하지 않고 setTimeout(0)으로 click 이후로 지연
- 기본 동작 명시적 차단
- e.preventDefault()로 텍스트 선택 방지
결과적으로 “드래그 중이었는가”가 아니라 “이 interaction에서 드래그가 발생했는가”를 기준으로 판단하게 됐다.
결과 (성공 / 실패)
결과: 성공
- 단순 클릭 → threshold 미만 이동 → hasDragged = false → 사이드 패널 정상 토글
- 드래그 → threshold 초과 → hasDragged = true → 위치 이동만 발생, 클릭 무시
- 이벤트 순서 문제 해결
- 브라우저 기본 동작 개입 제거
UI가 의도대로 반응하기 시작했다.
지금 다시 해본다면?
지금 다시 구현한다면, 이렇게 정리했을 것 같다.
“드래그 구현의 난이도는 좌표 계산이 아니라 의도 판별에 있다.”
그리고 초반부터 다음을 전제로 잡았을 것 같다.
- 드래그는 mousedown에서 시작되지 않는다
- 클릭과 드래그는 같은 제스처에서 갈라지는 결과다
- boolean 하나로 인터랙션을 설명하려 들면 반드시 꼬인다
이제는 isDragging을 UI 표현용으로, hasDragged를 사용자 의도 판별용으로 명확히 역할 분리해서 설계했을 것이다.
FAQ
- startY vs mousePositionRef
- startY: 드래그 시작점 (고정)
- mousePositionRef: 현재 위치 (변동)
- 이 둘의 차이가 임계값인 5px을 넘어야 실제 드래그로 인정
- 흐름은 다음과 같음
- onMouseDown: startY와 mousePositionRef 모두 초기화 (같은 값)
- onMouseMove: mousePositionRef만 계속 업데이트
- startY는 고정되어 있어서 “시작점에서 얼마나 움직였나” 비교 가능
- mousePositionRef는 실시간 위치라서 요소 위치 계산에 사용