노트

마우스 드래그 가로 스크롤

Drag to Scroll

프론트엔드#js#react · 연결된 개념 2개

쉽게 말하면

마우스 드래그 가로 스크롤은 휴대폰에서 손가락으로 밀던 가로 슬라이드를 데스크톱에서도 마우스로 잡아끌게 하는 패턴이에요. 책상 위 종이를 손바닥으로 밀듯, 끈 거리만큼 스크롤 위치를 옮겨요.

비유의 한계 mousedown만 걸면 끄는 동안 글자가 선택되거나 링크가 눌려요. 그래서 기본 동작을 막아요. 포인터가 컨테이너 밖으로 나가도 따라오도록 이동·종료 리스너는 document에 붙이고, 손을 떼면 같이 떼요.

터치 기기에서는 손가락으로 밀면 되는 가로 슬라이드를, 데스크톱에서는 마우스로 잡아끌어 스크롤하게 만드는 패턴. mousedown만으로 만들면 끌다가 안쪽 글자나 이미지가 선택되거나 링크가 눌리는 문제가 생긴다.

흐름

  1. 시작(dragstart나 mousedown): 시작 X 좌표와 현재 scrollLeft를 기억한다. 컨테이너의 pointer-events를 끄고 scroll-snap-type·scroll-behavior를 잠시 풀어 끄는 동안 튀지 않게 한다. 그리고 mousemove·mouseup 핸들러를 document에 붙인다(컨테이너 밖으로 나가도 계속 따라오도록)
  2. 이동: scrollLeft = 시작 scrollLeft - (현재 X - 시작 X)
  3. 끝(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()

연결된 개념

이 노트를 가리키는 문서

아직 없습니다.

뜻이 가까운 노트

  • 레이아웃 스래싱

    DOM을 바꾸고 곧바로 레이아웃 값을 읽기를 반복해서, 브라우저가 매번 리플로우를 강제로 실행하게 되는 현상. 쓰기 → 읽기 → 쓰기 → 읽기가 반복문 안에서 일어나면 요소 수만큼 레이아웃을 계산한다.

  • 브라우저 JS 메모리 누수 원인

    메모리 누수(Memory Leak)는 더 이상 쓰지 않는 메모리가 해제되지 않고 계속 남는 현상이다. 자바스크립트는 가비지 컬렉션(Garbage Collection, GC)으로 메모리를 자동으로 회수하므로, 누수는 대개 필요 없어진 객체를 가리키는 참조가 어딘가에 남아 있을 때 생긴다.

  • requestAnimationFrame

    "다음 화면을 그리기 직전에 이 함수를 실행해 달라"고 브라우저에 예약하는 API. 콜백은 태스크 큐도 마이크로태스크 큐도 아닌 별도 목록에 들어가고, 이벤트 루프의 렌더링 단계에서 페인트 직전에 한꺼번에 실행된다.

  • 레퍼런스 카운팅

    공유 자원을 쓰는 사용자 수를 세어, 수가 0에서 1이 될 때 자원을 준비하고 1에서 0이 될 때 정리하는 기법. 사무실에 처음 들어온 사람이 불을 켜고, 마지막에 나가는 사람이 끄는 규칙과 같다.

  • getBoundingClientRect

    요소가 지금 뷰포트(Viewport) 안 어디에 얼마만 한 크기로 그려져 있는지를 DOMRect(x, y, top, right, bottom, left, width, height)로 돌려주는 DOM(Document Object Model) API. 문서 기준이 아니라 현재 화면 기준이다.

보기 옵션