Motion
제스처 훅
stable스와이프, 핀치, 드래그, 탭 등 제스처 이벤트와 제스처 기반 애니메이션 훅
@hua-labs/motion-corev2.0.0
Import
import { useGesture, useGestureMotion } from '@hua-labs/motion-core'useGesture
터치/마우스 이벤트를 추상화하여 스와이프, 핀치, 로테이트, 팬, 탭, 더블탭, 롱프레스 등 다양한 제스처를 감지한다. 웹과 모바일 모두 지원한다.
Signature
function useGesture(options?: GestureOptions): GestureReturnGestureOptions
| 파라미터 | 타입 | 기본값 | 설명 |
|---|---|---|---|
enabled | boolean | true | 제스처 활성화 |
threshold | number | 10 | 제스처 인식 최소 거리 (px) |
timeout | number | 300 | 탭 인식 타임아웃 (ms) |
swipeThreshold | number | 50 | 스와이프 최소 거리 (px) |
swipeVelocity | number | 0.3 | 스와이프 최소 속도 |
swipeDirections | Direction[] | 4방향 | 허용 스와이프 방향 |
pinchThreshold | number | 10 | 핀치 최소 거리 변화 |
minScale | number | 0.1 | 최소 핀치 스케일 |
maxScale | number | 10 | 최대 핀치 스케일 |
rotateThreshold | number | 5 | 로테이트 최소 각도 (deg) |
panThreshold | number | 10 | 팬 최소 거리 (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 |
|---|---|
hover | scale(1.05) translateY(-2px) |
drag | translate(deltaX, deltaY) |
pinch | scale(scale) |
swipe | translateX(deltaX) rotateY(deltaX * 0.1deg) |
tilt | rotateX(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개 이상 터치 포인트)가 필요하다.
useGestureMotion의ref는useRef가 아닌useRef<HTMLElement | null>(null)타입이다. TypeScript 사용 시 타입 단언이 필요할 수 있다.- 롱프레스는 500ms 고정이다. 변경이 필요하면
useGesture를 직접 사용하고onLongPress콜백에서 처리한다.