Skip to content
Docs
Motion

제스처 훅

stable

스와이프, 핀치, 드래그, 탭 등 제스처 이벤트와 제스처 기반 애니메이션 훅

Import

import { useGesture, useGestureMotion } from '@hua-labs/motion-core'

useGesture

터치/마우스 이벤트를 추상화하여 스와이프, 핀치, 로테이트, 팬, 탭, 더블탭, 롱프레스 등 다양한 제스처를 감지한다. 웹과 모바일 모두 지원한다.

Signature

function useGesture(options?: GestureOptions): GestureReturn

GestureOptions

파라미터타입기본값설명
enabledbooleantrue제스처 활성화
thresholdnumber10제스처 인식 최소 거리 (px)
timeoutnumber300탭 인식 타임아웃 (ms)
swipeThresholdnumber50스와이프 최소 거리 (px)
swipeVelocitynumber0.3스와이프 최소 속도
swipeDirectionsDirection[]4방향허용 스와이프 방향
pinchThresholdnumber10핀치 최소 거리 변화
minScalenumber0.1최소 핀치 스케일
maxScalenumber10최대 핀치 스케일
rotateThresholdnumber5로테이트 최소 각도 (deg)
panThresholdnumber10팬 최소 거리 (px)
onSwipe(dir, dist, vel) => void스와이프 콜백
onPinch(scale, delta) => void핀치 콜백
onRotate(angle, delta) => void로테이트 콜백
onPan(dx, dy, tx, ty) => void팬 콜백
onTap(x, y) => void탭 콜백
onDoubleTap(x, y) => void더블탭 콜백
onLongPress(x, y) => void롱프레스 콜백 (500ms)
onStart(x, y) => void제스처 시작 콜백
onMove(x, y) => void제스처 이동 콜백
onEnd(x, y) => void제스처 종료 콜백

GestureReturn

interface GestureReturn {
  // 상태
  isActive: boolean
  gesture: string | null    // 'pan' | 'pinch' | 'rotate' | 'swipe-up' | ...
  scale: number
  rotation: number
  deltaX: number
  deltaY: number
  distance: number
  velocity: number

  // 제어
  start: () => void
  stop: () => void
  reset: () => void

  // 이벤트 핸들러 (JSX에 직접 전달)
  onTouchStart: (e: React.TouchEvent | TouchEvent) => void
  onTouchMove: (e: React.TouchEvent | TouchEvent) => void
  onTouchEnd: (e: React.TouchEvent | TouchEvent) => void
  onMouseDown: (e: React.MouseEvent | MouseEvent) => void
  onMouseMove: (e: React.MouseEvent | MouseEvent) => void
  onMouseUp: (e: React.MouseEvent | MouseEvent) => void
}

Usage — 스와이프 감지

import { useGesture } from '@hua-labs/motion-core'

function SwipeCard() {
  const gesture = useGesture({
    swipeThreshold: 80,
    onSwipe: (direction, distance, velocity) => {
      console.log(`${direction} 스와이프: ${distance}px, ${velocity}px/s`)
    }
  })

  return (
    <div
      onTouchStart={gesture.onTouchStart}
      onTouchMove={gesture.onTouchMove}
      onTouchEnd={gesture.onTouchEnd}
      onMouseDown={gesture.onMouseDown}
      onMouseMove={gesture.onMouseMove}
      onMouseUp={gesture.onMouseUp}
      style={{ userSelect: 'none', padding: 40 }}
    >
      스와이프해 보세요
    </div>
  )
}

Usage — 핀치 줌

import { useGesture } from '@hua-labs/motion-core'

function PinchImage() {
  const { scale, onTouchStart, onTouchMove, onTouchEnd } = useGesture({
    onPinch: (scale) => {
      console.log('스케일:', scale)
    }
  })

  return (
    <img
      src="/photo.jpg"
      style={{ transform: `scale(${scale})`, touchAction: 'none' }}
      onTouchStart={onTouchStart}
      onTouchMove={onTouchMove}
      onTouchEnd={onTouchEnd}
    />
  )
}

Usage — 탭 + 더블탭

import { useGesture } from '@hua-labs/motion-core'

function TapArea() {
  const gesture = useGesture({
    onTap: (x, y) => console.log(`탭: ${x}, ${y}`),
    onDoubleTap: (x, y) => console.log(`더블탭: ${x}, ${y}`),
    onLongPress: (x, y) => console.log(`롱프레스: ${x}, ${y}`)
  })

  return (
    <div
      onTouchStart={gesture.onTouchStart}
      onTouchEnd={gesture.onTouchEnd}
      onMouseDown={gesture.onMouseDown}
      onMouseUp={gesture.onMouseUp}
    >
      터치 영역
    </div>
  )
}

useGestureMotion

제스처 타입에 맞는 CSS transform 스타일을 자동으로 계산하여 반환한다. useGesture보다 높은 수준의 추상화다.

Signature

function useGestureMotion(options: GestureMotionOptions): {
  ref: RefObject<HTMLElement | null>
  gestureState: GestureState
  motionStyle: CSSProperties
  isActive: boolean
}

interface GestureMotionOptions {
  gestureType: 'hover' | 'drag' | 'pinch' | 'swipe' | 'tilt'
  duration?: number         // CSS transition 지속 시간 (기본값: 300ms)
  easing?: string           // CSS 이징 (기본값: 'ease-out')
  sensitivity?: number      // 감도 배율 (기본값: 1)
  enabled?: boolean         // 활성화 (기본값: true)
  onGestureStart?: () => void
  onGestureEnd?: () => void
}

GestureState

interface GestureState {
  isActive: boolean
  x: number
  y: number
  deltaX: number
  deltaY: number
  scale: number
  rotation: number
}

gestureType별 적용 transform

gestureType활성 시 transform
hoverscale(1.05) translateY(-2px)
dragtranslate(deltaX, deltaY)
pinchscale(scale)
swipetranslateX(deltaX) rotateY(deltaX * 0.1deg)
tiltrotateX(deltaY * 0.1deg) rotateY(deltaX * 0.1deg)

Usage

import { useGestureMotion } from '@hua-labs/motion-core'

function DraggableCard() {
  const { ref, motionStyle, isActive } = useGestureMotion({
    gestureType: 'drag',
    sensitivity: 1.5,
    onGestureStart: () => console.log('드래그 시작'),
    onGestureEnd: () => console.log('드래그 종료')
  })

  return (
    <div
      ref={ref}
      style={{
        ...motionStyle,
        width: 200,
        height: 200,
        background: isActive ? '#e0f2fe' : '#f0f9ff',
        cursor: 'grab'
      }}
    >
      드래그해 보세요
    </div>
  )
}

Usage — 틸트 효과

import { useGestureMotion } from '@hua-labs/motion-core'

function TiltCard() {
  const { ref, motionStyle } = useGestureMotion({
    gestureType: 'tilt',
    sensitivity: 2
  })

  return (
    <div ref={ref} style={{ ...motionStyle, perspective: 1000 }}>
      기울여 보세요
    </div>
  )
}

Notes

  • useGesture의 이벤트 핸들러(onTouchStart 등)는 passive 이벤트 리스너에서 preventDefault를 호출하지 않으므로 터치 스크롤과 충돌하지 않는다.
  • 핀치/로테이트는 멀티터치(2개 이상 터치 포인트)가 필요하다.
  • useGestureMotionrefuseRef가 아닌 useRef<HTMLElement | null>(null) 타입이다. TypeScript 사용 시 타입 단언이 필요할 수 있다.
  • 롱프레스는 500ms 고정이다. 변경이 필요하면 useGesture를 직접 사용하고 onLongPress 콜백에서 처리한다.