터치 기기에서는 손가락으로 밀면 되는 가로 슬라이드를, 데스크톱에서는 마우스로 잡아끌어 스크롤하게 만드는 패턴. mousedown만으로 만들면 끌다가 안쪽 글자나 이미지가 선택되거나 링크가 눌리는 문제가 생긴다.
흐름
- 시작(
dragstart나mousedown): 시작 X 좌표와 현재scrollLeft를 기억한다. 컨테이너의pointer-events를 끄고scroll-snap-type·scroll-behavior를 잠시 풀어 끄는 동안 튀지 않게 한다. 그리고mousemove·mouseup핸들러를document에 붙인다(컨테이너 밖으로 나가도 계속 따라오도록) - 이동:
scrollLeft = 시작 scrollLeft - (현재 X - 시작 X) - 끝(
mouseup): 끈 스타일을 되돌리고document에 붙인 핸들러를 뗀다
function useDragScroll(ref: React.RefObject<HTMLElement>) {
useEffect(() => {
const el = ref.current
if (!el) return
let startX = 0, startLeft = 0
const onMove = (e: MouseEvent) => { el.scrollLeft = startLeft - (e.clientX - startX) }
const onUp = () => {
el.style.pointerEvents = el.style.scrollSnapType = ''
document.removeEventListener('mousemove', onMove)
document.removeEventListener('mouseup', onUp)
}
const onDown = (e: MouseEvent) => {
e.preventDefault() // 글자 선택·이미지 드래그 방지
startX = e.clientX
startLeft = el.scrollLeft
el.style.pointerEvents = 'none'
el.style.scrollSnapType = 'none'
document.addEventListener('mousemove', onMove)
document.addEventListener('mouseup', onUp)
}
el.addEventListener('mousedown', onDown)
return () => { el.removeEventListener('mousedown', onDown); onUp() }
}, [ref])
}- 이 훅은 DOM(Document Object Model)을 직접 만지는 Effect다. 정리 함수에서 붙인 리스너를 반드시 떼야 한다 → 부수 효과와 Effect
document리스너를 필요할 때만 붙이는 방식은 이벤트 위임과 반대 방향의 선택이다. 끄는 동안에만 전역에서 듣는다- 끈 거리가 짧으면 클릭으로 보고, 길면 클릭을 막는 처리를 더하면 슬라이드 안의 링크도 함께 쓸 수 있다
- 요즘은 Pointer Events(
pointerdown,setPointerCapture)로 마우스·터치·펜을 한 번에 다루는 편이 간단하다
참고: MDN — Pointer events · MDN — Element: setPointerCapture()