ryong.log
2026년 2월 27일·읽는 데 약 2분

BottomSheet Drag Close Handler 적용 가이드

관련 글

숫자 피커에 물리 법칙을 입히다 — React에서 관성 스크롤 구현기

2026. 2. 27.

OpenClaw를 활용한 hotfix 대응구축팀 설계

2026. 3. 6.

OpenRun Bot — RAG 기반 챗봇 서버 구축기

2026. 2. 16.

BottomSheet Drag Close를 제품 수준으로 적용하기

이 문서는 BottomSheet 상단 핸들을 아래로 드래그해 닫는 UX를 실제 서비스에 안정적으로 적용하기 위한 정리본입니다.

  • 데스크톱(마우스) + 모바일(터치) 동시 지원
  • 드래그 임계값 기반 닫기 판정
  • 드래그 중 실시간 이동 + 종료 시 부드러운 복귀/퇴장

요구사항 정리

  1. 핸들을 누르고 아래로 드래그하면 시트가 손가락을 따라온다.
  2. 임계값(예: 20px) 이상이면 닫기 애니메이션 후 onClose 호출
  3. 임계값 미만이면 원래 위치로 복귀
  4. 드래그가 취소/이탈되어도 스타일 상태가 꼬이지 않는다.

필수 상태와 Ref

typescript
Copied
1const [isDragging, setIsDragging] = useState(false)23const startYRef = useRef(0)4const deltaYRef = useRef(0)5const isClosingRef = useRef(false)
  • isDragging: 드래그 진행 상태
  • startYRef: 시작 지점
  • deltaYRef: 현재 누적 이동 거리
  • isClosingRef: 중복 close 호출 방지

핵심 핸들러

1) Start

typescript
Copied
1const handleDragStart = (clientY: number) => {2  if (isClosingRef.current) return3  setIsDragging(true)4  startYRef.current = clientY5  deltaYRef.current = 06}

2) Move

typescript
Copied
1const handleDragMove = (clientY: number, el: HTMLElement) => {2  if (!isDragging || isClosingRef.current) return34  const delta = clientY - startYRef.current5  deltaYRef.current = Math.max(0, delta)67  el.style.transitionDuration = '0ms'8  el.style.transform = `translate3d(0, ${deltaYRef.current}px, 0)`9}

3) End

typescript
Copied
1const handleDragEnd = (el: HTMLElement, onClose?: () => void) => {2  const shouldClose = deltaYRef.current >= 2034  if (shouldClose && onClose) {5    isClosingRef.current = true6    el.style.transitionDuration = '300ms'7    el.style.transform = 'translate3d(0, 100%, 0)'8    window.setTimeout(() => onClose(), 300)9  } else {10    el.removeAttribute('style')11  }1213  setIsDragging(false)14  startYRef.current = 015  deltaYRef.current = 016}

서비스 구조상 onClose가 즉시 언마운트를 유발한다면, 애니메이션 완료 후 onClose를 호출하는 2단계 닫기 패턴이 필수입니다.


JSX 이벤트 연결 패턴

typescript
Copied
1<StyledBottomSheet2  onMouseMove={handleMouseMove}3  onMouseUp={handleMouseUp}4  onMouseOut={handleMouseOut}5  onTouchMove={handleTouchMove}6  onTouchEnd={handleTouchEnd}7  onTouchCancel={handleTouchCancel}8>9  <DragBar onMouseDown={handleMouseDown} onTouchStart={handleTouchStart}>10    <DragHandle />11  </DragBar>12  {children}13</StyledBottomSheet>
  • 드래그 시작은 DragBar에서만 받는다.
  • 이동/종료는 BottomSheet 전체에서 받아, 손가락 이탈 상황을 흡수한다.

스타일 포인트

  • 드래그 중 cursor: grabbing, 평상시 grab로 피드백 제공
  • transform 애니메이션은 translate3d 사용으로 GPU 가속 유도
  • 핸들 터치 영역 padding(최소 12px 이상) 확보
  • 내부 클릭 이벤트는 stopPropagation으로 backdrop close와 분리

적용 체크리스트

  1. 닫기 임계값과 transitionDuration을 상수로 관리한다.
  2. 드래그 중 스크롤 컨테이너 충돌 여부를 확인한다.
  3. 모바일 Safari에서 touch 이벤트 동작을 실기기 검증한다.
  4. 중복 close 호출 방지 플래그(isClosingRef)를 둔다.
  5. 언마운트 시 타이머/스타일 상태를 정리한다.

동작 흐름

plain text
Copied
1Drag start2  -> track delta3  -> if delta >= threshold: slide-down animation -> onClose4  -> else: restore to origin

마무리

“

좋은 BottomSheet는 단순히 닫히는 컴포넌트가 아니라, 사용자의 손동작과 일관되게 반응하는 인터랙션 컴포넌트입니다.

  • BottomSheet Drag Close Handler 적용 가이드
  • BottomSheet Drag Close를 제품 수준으로 적용하기
  • 요구사항 정리
  • 필수 상태와 Ref
  • 핵심 핸들러
  • 1) Start
  • 2) Move
  • 3) End
  • JSX 이벤트 연결 패턴
  • 스타일 포인트
  • 적용 체크리스트
  • 동작 흐름
  • 마무리