# ZoomableImage

페이지 이미지를 두 손가락으로 1배에서 4배까지 확대하고, 확대한 동안에만 끌어서 움직이며, 두 번 눌러 확대·축소하는 이미지다.

- 분류: 학습 콘텐츠
- 가져오기: `import { ZoomableImage } from '@/ui/ZoomableImage'`
- 코드 위치: `apps/mobile/src/ui/ZoomableImage.tsx:16` (Mordy 저장소 60df633)
- 웹 페이지: https://developers.moring.co/tds/components/zoomable-image

## 미리보기

실제 컴포넌트를 그린 화면은 https://developers.moring.co/tds/components/zoomable-image 에서 볼 수 있다. 서비스 색 선택기로 Mordy, Morit 색을 바꿔 볼 수 있다.

## 언제 쓰는가

### 쓴다
- 원본 자료 페이지나 게시물 이미지를 크게 볼 때 쓴다. 문서 원본 보기 화면과 커뮤니티 게시물 미디어가 쓴다.
- 확대한 동안 옆 페이지로 넘기는 제스처를 막으려면 `onZoomChange`로 알아내서 부모의 스와이프를 끈다.
- 이미지 위에 표시를 얹으려면 `overlay`를 쓴다.

### 쓰지 않는다
- 작은 썸네일에는 쓰지 않는다. 제스처가 목록 스크롤과 겹친다.
- 4배보다 더 키우려고 `ZoomHandle`을 건드리지 않는다.

## 종류

종류가 없다. 확대 비율은 1~4배, 두 번 누르면 2.5배다.

## 크기

| 이름 | 값 | 설명 |
| --- | --- | --- |
| 기본 | `width` × `height` | 필수. 이미지가 이 상자 안에서 보인다. |

## 상태

| 상태 | 모습과 동작 |
| --- | --- |
| 1배 | 끌기가 꺼져 있어 부모의 가로 스와이프(페이지 넘기기)가 동작한다. |
| 확대 | 손가락 위치를 중심으로 커지고, 확대한 동안에만 끌어서 움직인다. 가장자리는 이미지 밖이 보이지 않게 제한된다. |
| 두 번 누름 | 1배 → 2.5배(220ms), 확대 상태 → 1배(200ms). |
| 초기화 | `ref.reset()`을 부르면 180ms 동안 1배로 돌아온다. |

## 사용 방법

### 기본

```tsx
import { ZoomableImage, type ZoomHandle } from '@/ui/ZoomableImage';

const zoom = useRef<ZoomHandle>(null);

<ZoomableImage ref={zoom} uri={pageUrl} width={width} height={height} onZoomChange={setZoomed} label="3쪽" />
```

### 속성(props)

| 이름 | 타입 | 기본값 | 필수 | 설명 |
| --- | --- | --- | --- | --- |
| `uri` | `string` | — | 필수 | 이미지 주소. |
| `width` | `number` | — | 필수 | 상자 폭(px). 필수. |
| `height` | `number` | — | 필수 | 상자 높이(px). 필수. |
| `onZoomChange` | `(zoomed: boolean) => void` | — | 선택 | 확대 상태가 바뀔 때 `true`/`false`와 함께 호출된다. |
| `overlay` | `ReactNode` | — | 선택 | 이미지 위에 얹을 요소(표시, 라벨). |
| `label` | `string` | — | 선택 | 접근성 이름. |
| `onSize` | `(w: number, h: number) => void` | — | 선택 | 이미지 원본 크기를 알게 되었을 때 호출된다. |
| `headers` | `Record<string, string>` | — | 선택 | 비공개 저장소 이미지를 불러올 때 쓰는 요청 헤더. |

`ref`로 `{ reset() }`을 받을 수 있다.

## 치수와 토큰

| 항목 | 값 |
| --- | --- |
| 확대 | 최대 4배, 두 번 누르면 2.5배 |
| 시간 | 두 번 누름 220ms, 되돌림 200ms, 초기화·한계 보정 180ms |

## 접근성

- 제스처가 필요한 컴포넌트다. 확대 버튼이나 키보드 같은 대체 방법이 앱 코드에는 없다. 웹에서 쓸 때는 확대·축소 버튼을 함께 둔다(이 문서의 미리보기처럼).
- 이미지의 이름은 `label`이다.

## 잘한 예와 잘못한 예

- 잘한 예: 확대한 동안 부모의 스와이프를 끈다.
- 잘못한 예: 확대할 방법이 제스처뿐이면 손을 쓰기 어려운 사용자가 쓸 수 없다. 웹에서는 버튼을 더한다.

## 함께 보는 컴포넌트

- [ImageBlock](https://developers.moring.co/tds/components/image-block.md): 문제에 쓰인 원본 자료의 한 영역을 오려 보여 준다. 영역 위에 라벨과 강조 고리를 얹을 수 있고, 눌러서 크게 볼 수 있다.
- [SourceCrop](https://developers.moring.co/tds/components/source-crop.md): 문제가 나온 원본 자료의 근거 부분을 보여 준다. 근거가 있는 줄을 가운데로 한 가로 띠, 또는 전체 페이지에 근거 영역 표시를 얹은 모양이다.
