노트

마우스 드래그 가로 스크롤

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

  • requestAnimationFrame

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

  • 레퍼런스 카운팅

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

  • getBoundingClientRect

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

  • 이벤트 루프

    자바스크립트를 실행하는 하나의 스레드가 할 일을 큐에서 하나씩 꺼내 처리하는 반복 구조. JS 엔진 자체에는 타이머도 네트워크도 없고, 브라우저(또는 Node.js)가 이 루프를 돌리면서 콜백을 엔진에 넘겨준다.

보기 옵션