ImageViewer
전체 화면 미디어 라이트박스 — 이미지·영상 사이 이동, 하단 썸네일 필름스트립, 줌·회전, 다운로드·삭제를 내장한 오버레이 뷰어입니다.
마지막 업데이트 2026-07-02
한눈에#
이미지·영상을 전체 화면 스테이지에서 크게 보는 라이트박스입니다 — Apple 사진
관용구를 따라 여러 항목 사이를 ←/→나 하단 썸네일 필름스트립으로 이동하고,
이미지는 줌(0.25~4x)·90° 회전·드래그 팬으로 살펴보며, 영상은 네이티브 컨트롤로
재생합니다. 액션바에서 다운로드(삭제는 옵션)까지 이어집니다. 표면은 WDS
라이트/다크 테마를 따르고, Modal과 같은 오버레이 계약(포커스 트랩·Escape 닫기·
inert 배경)을 공유합니다.
물리 기반 모션 — 열릴 때 스프링 등장, 항목 전환은 depth 슬라이드(나가는 장면
축소·들어오는 장면 확대), 이미지를 아래로 끌면 닫히고 좌우로 끌면 넘어갑니다
(투영 착지점·고무줄 저항 판정). 모두 WDS 모션 엔진(spring.spatial/effect·
projectMomentum·rubberBandOffset)을 재사용하며 prefers-reduced-motion에서
정지합니다.
트리거를 눌러 등장 스프링·필름스트립·depth 전환·드래그 제스처·영상 재생을 직접 확인하세요:
사용 시점#
첨부 이미지·영상·갤러리처럼 원본을 크게 확인해야 하는 곳이면 ImageViewer — 문서 등 재생 불가 콘텐츠는 다른 표면입니다.
- 쓴다 — FileUpload 미리보기(내장 합성), 갤러리·썸네일 그리드의 원본 확대, 이미지·영상 자체가 콘텐츠일 때(사진 앱 스타일 필름스트립 탐색)
- 안 쓴다 — 확인·결정이 필요한 다이얼로그(
Modal), PDF·문서 뷰어(전용 표면 필요), 인라인 확대(팝업이 과할 때)
변형·상태#
단일 컴포넌트입니다 — items가 2개 이상이면 좌우 내비게이션 버튼·1 / n
카운터·하단 필름스트립이 자동 노출되고, 1개면 숨겨집니다. index+
onIndexChange를 주면 제어 모드(부모가 진실의 원천), 없으면 defaultIndex로
시작하는 비제어입니다.
- 미디어 종류 — 항목의
kind가'video'면 네이티브 컨트롤(재생·탐색·볼륨· 전체화면)로 렌더되고 줌/회전 툴바는 숨겨집니다. 기본'image' - 필름스트립 — 하단 썸네일 행. 활성 항목은 밝게·테두리로 강조되고 가운데로 자동 스크롤되며, 영상 썸네일엔 재생 배지가 얹힙니다. 클릭으로 즉시 전환
- 줌(이미지) — 툴바 버튼·키보드
+/-/0·마우스 휠. 0.25~4x, 25% 단계. 현재 배율은 툴바 가운데 읽힌다 - 회전(이미지) — 90°씩 시계 방향
- 팬(이미지) — 100% 초과 줌에서 드래그로 이동(
grab커서) - 액션 — 다운로드는 항상, 삭제는
onDelete를 줄 때만 액션바에 노출(현재 항목 인덱스로 호출) - 리셋 — 항목 전환·재열림 시 줌/회전/팬이 원래대로
- 모션·제스처(이미지) — 열릴 때 스프링 등장, 항목 전환은 depth 슬라이드
(
useViewTransition). 기본 배율에서 아래로 드래그=닫기(투영 착지점 25% 또는 플릭 속도 판정), 좌우 드래그=넘기기(경계는 고무줄 저항). 모두 WDS 모션 엔진 재사용·prefers-reduced-motion에서 정지
항목의 name은 헤더 캡션·필름스트립 라벨·dialog 접근성 이름이 되고,
description(용량·형식 등)은 하단 캡션으로 렌더됩니다.
API#
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
open | boolean | — | 표시 여부 — 제어 prop |
onClose | () => void | — | Escape·닫기 버튼·배경 클릭의 닫힘 요청 콜백 |
items | readonly ImageViewerItem[] | — | 표시할 미디어 목록 — 2개 이상이면 ←/→ 내비·카운터·필름스트립 노출 |
index | number | — | 제어 현재 인덱스 — 제공하면 전환은 onIndexChange로만 통지 |
defaultIndex | number | 0 | 초기 인덱스 (비제어, index 미제공 시) |
onIndexChange | (index: number) => void | — | 인덱스 전환 통지 — 제어·비제어 공통 |
onDelete | (index: number) => void | — | 제공 시 액션바에 삭제 버튼 노출 — 현재 항목 인덱스로 호출(닫힘·이동은 소비자 소관) |
closeLabel | string | 닫기 | 닫기 버튼 aria-label |
ref | Ref<HTMLDivElement> | — | 패널(role=dialog)로 병합 전달되는 ref (React 19 ref-as-prop) |
접근성#
role="dialog" + aria-modal로 열리고 이름은 현재 항목의 name입니다. 열리는
동안 포커스가 뷰어 안에 갇히고 배경은 inert 처리되며, 닫히면 트리거로 포커스가
복원됩니다(Modal과 동일 계약). 카운터는 aria-live="polite"로 항목 전환을
낭독합니다.
| 키 | 동작 |
|---|---|
← / → | 이전/다음 항목 (양 끝 클램프) |
+ / - | 확대/축소 (0.25~4x, 이미지 전용) |
0 | 원래 크기·회전·위치 리셋 (이미지 전용) |
Escape | 닫기 |
필름스트립 썸네일은 aria-current로 현재 항목을 노출하고 각 버튼은 항목
name으로 라벨링됩니다. 영상은 브라우저 네이티브 컨트롤(키보드 조작 포함)을
따릅니다. 모든 크롬 버튼(내비·줌·회전·다운로드·삭제·닫기)은 44px 터치
타깃이며, 빈 스테이지 클릭은 기본 배율에서만 닫기로 동작합니다(줌 상태에선
팬과 충돌 방지). 드래그 제스처(닫기·넘기기)는 키보드·버튼으로 모두 대체
가능하며, prefers-reduced-motion에서는 등장·전환·스프링백 애니메이션이
정지하고 즉시 상태만 바뀝니다.
토큰#
표면은 WDS 라이트/다크 테마 토큰을 따릅니다 — 베일은 color.bg
92%(color-mix), 잉크는 color.text/text-subtle, 액션바는 color.surface+
color.border+shadow.lg, 필름스트립 썸네일은 space.16 정사각·radius.md에
활성은 color.primary 테두리, 삭제 hover는 color.error 틴트입니다. 모션은
spring.spatial(ζ0.8 — 등장·depth 전환·필름스트립 정착)과 spring.effect
(ζ1.0 — 줌·드래그 스프링백)을 linear() 이징으로 쓰며,
prefers-reduced-motion에서 정지합니다.
관련#
- FileUpload — 미리보기가 이 컴포넌트를 내장 합성
- Modal — 결정형 오버레이 (같은 포커스/닫힘 계약)