Skip to content
Docs
Motion

유틸리티 훅

stable

뷰포트 감지, 마우스 추적, 접근성, 윈도우 크기 추적 유틸리티 훅

Import

import {
  useInView,
  useMouse,
  useReducedMotion,
  useReducedMotionObject,
  useWindowSize,
} from '@hua-labs/motion-core'

useInView

Intersection Observer를 사용하여 요소가 뷰포트에 보이는지 감지한다. 스크롤 애니메이션, 레이지 로딩 등에 활용한다.

Signature

function useInView<T extends HTMLElement = HTMLDivElement>(
  options?: InViewOptions
): InViewReturn<T>

interface InViewOptions {
  threshold?: number | number[]   // 기본값: 0
  rootMargin?: string             // 기본값: '0px'
  triggerOnce?: boolean           // 기본값: false
  initialInView?: boolean         // 기본값: false
}

interface InViewReturn<T extends HTMLElement = HTMLDivElement> {
  ref: RefObject<T | null>
  inView: boolean
  entry: IntersectionObserverEntry | null
}

Parameters

파라미터타입기본값설명
threshold`number \number[]`0IntersectionObserver 임계값
rootMarginstring'0px'루트 마진 (CSS margin 형식)
triggerOncebooleanfalse처음 감지 후 Observer 해제
initialInViewbooleanfalse초기 가시성 상태

Returns

이름타입설명
refRefObject<T>감지할 DOM 요소에 연결할 ref
inViewboolean현재 뷰포트에 보이는지 여부
entry`IntersectionObserverEntry \null`마지막 Observer 엔트리

Usage

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

function LazyImage() {
  const { ref, inView } = useInView({
    threshold: 0.1,
    triggerOnce: true
  })

  return (
    <div ref={ref}>
      {inView ? (
        <img src="/photo.jpg" alt="사진" />
      ) : (
        <div style={{ height: 300, background: '#f0f0f0' }} />
      )}
    </div>
  )
}
// 스크롤 애니메이션에 활용
function AnimatedSection() {
  const { ref, inView } = useInView({ threshold: 0.2 })

  return (
    <section
      ref={ref}
      style={{
        opacity: inView ? 1 : 0,
        transform: inView ? 'translateY(0)' : 'translateY(20px)',
        transition: 'all 600ms ease-out'
      }}
    >
      섹션 내용
    </section>
  )
}

useMouse

마우스 위치를 실시간으로 추적한다. 커서 따라다니는 효과, 마우스 기반 인터랙션 등에 활용한다.

Signature

function useMouse(options?: MouseOptions): MouseReturn

interface MouseOptions {
  targetRef?: RefObject<HTMLElement | null>  // 특정 요소 내 상대 좌표 계산용
  throttle?: number                          // 업데이트 스로틀 (ms, 기본값: 0)
}

interface MouseReturn {
  x: number           // viewport 기준 X 좌표 (px)
  y: number           // viewport 기준 Y 좌표 (px)
  elementX: number    // 요소 내 상대 X 좌표 (0~1)
  elementY: number    // 요소 내 상대 Y 좌표 (0~1)
  isOver: boolean     // 마우스가 타겟 요소 위에 있는지 여부
}

Parameters

파라미터타입기본값설명
targetRefRefObject<HTMLElement>상대 좌표를 계산할 기준 요소
throttlenumber0업데이트 스로틀 시간 (ms)

targetRef를 지정하면 해당 요소 내에서의 상대 좌표(0~1)도 함께 계산한다. 지정하지 않으면 window 전체를 추적한다.

Usage

import { useRef } from 'react'
import { useMouse } from '@hua-labs/motion-core'

function MouseParallax() {
  const containerRef = useRef<HTMLDivElement>(null)
  const { elementX, elementY } = useMouse({
    targetRef: containerRef,
    throttle: 16  // ~60fps
  })

  const offsetX = (elementX - 0.5) * 20  // -10 ~ 10
  const offsetY = (elementY - 0.5) * 20

  return (
    <div ref={containerRef} style={{ position: 'relative', overflow: 'hidden' }}>
      <div
        style={{
          transform: `translate(${offsetX}px, ${offsetY}px)`,
          transition: 'transform 100ms ease-out'
        }}
      >
        패럴랙스 레이어
      </div>
    </div>
  )
}
// 전역 마우스 위치
function CursorTracker() {
  const { x, y } = useMouse()

  return (
    <div
      style={{
        position: 'fixed',
        left: x - 10,
        top: y - 10,
        width: 20,
        height: 20,
        borderRadius: '50%',
        background: 'rgba(0,0,0,0.3)',
        pointerEvents: 'none'
      }}
    />
  )
}

useReducedMotion

사용자의 prefers-reduced-motion 접근성 설정을 감지한다. boolean을 직접 반환하는 가벼운 버전이다.

Signature

function useReducedMotion(): boolean

Returns

true이면 사용자가 모션 감소를 선호하므로 애니메이션을 줄이거나 비활성화해야 한다.

Usage

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

function AnimatedBanner() {
  const prefersReducedMotion = useReducedMotion()

  return (
    <div
      style={{
        animation: prefersReducedMotion
          ? 'none'
          : 'slideIn 600ms ease-out forwards'
      }}
    >
      배너
    </div>
  )
}

useReducedMotionObject

{ prefersReducedMotion: boolean } 객체 형태로 반환한다. 이전 버전과의 호환을 위해 유지된다.

Signature

function useReducedMotionObject(): { prefersReducedMotion: boolean }

신규 코드에서는 useReducedMotion()을 사용하는 것을 권장한다.

Usage

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

function LegacyComponent() {
  const { prefersReducedMotion } = useReducedMotionObject()
  return <div>{prefersReducedMotion ? '모션 없음' : '모션 있음'}</div>
}

useWindowSize

브라우저 윈도우 크기 변경을 감지한다. 반응형 레이아웃, 리사이즈 기반 애니메이션 등에 활용한다.

Signature

function useWindowSize(options?: WindowSizeOptions): WindowSizeReturn

interface WindowSizeOptions {
  debounce?: number       // 디바운스 시간 (ms, 기본값: 100)
  initialWidth?: number   // 초기 너비 (SSR용, 기본값: 0)
  initialHeight?: number  // 초기 높이 (SSR용, 기본값: 0)
}

interface WindowSizeReturn {
  width: number     // 현재 윈도우 너비 (px)
  height: number    // 현재 윈도우 높이 (px)
  isMounted: boolean // 클라이언트 마운트 여부
}

Parameters

파라미터타입기본값설명
debouncenumber100resize 이벤트 디바운스 시간 (ms)
initialWidthnumber0SSR 초기 너비
initialHeightnumber0SSR 초기 높이

Returns

이름타입설명
widthnumber윈도우 너비 (px)
heightnumber윈도우 높이 (px)
isMountedboolean클라이언트 마운트 완료 여부

Usage

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

function ResponsiveComponent() {
  const { width, height, isMounted } = useWindowSize({ debounce: 200 })

  if (!isMounted) return null  // SSR 대응

  const isMobile = width < 768

  return (
    <div>
      <p>뷰포트: {width} × {height}</p>
      <p>{isMobile ? '모바일' : '데스크탑'}</p>
    </div>
  )
}

Notes

  • useInView는 내부적으로 sharedIntersectionObserver 풀을 사용하므로 동일 요소에 여러 Observer가 등록되어도 인스턴스가 공유된다.
  • useMouseelementX, elementY는 0~1 범위로 클램프된다.
  • useReducedMotion은 SSR에서 항상 false를 반환한다. 클라이언트 마운트 후 실제 값으로 업데이트된다.
  • useWindowSizeisMountedfalse인 동안에는 widthheightinitialWidth/initialHeight 값을 갖는다.