---
url: /2.x/ko/guide/getting-started/overview.md
---
> For AI agents: the complete documentation index is available at /2.x/ko/llms.txt, the full documentation bundle is available at /2.x/ko/llms-full.txt.
# 개요
React Native에서 이미지 갤러리나 콘텐츠 뷰어를 구현할 때, 복잡한 제스처 처리와 애니메이션 구현으로 어려움을 겪으신 적이 있으신가요?
기존 라이브러리들은 커스터마이징이 어렵거나 성능 문제가 있었습니다. `react-native-gesture-image-viewer`는 [**React Native Reanimated**](https://docs.swmansion.com/react-native-reanimated/)와 [**Gesture Handler**](https://docs.swmansion.com/react-native-gesture-handler/docs/)를 기반으로 한 고성능 **범용 제스처 뷰어** 라이브러리로, 이미지뿐만 아니라 비디오, 커스텀 컴포넌트 등 모든 콘텐츠에 완전한 커스터마이징과 직관적인 제스처 지원을 제공합니다.
## 주요 특징
- 🤌 **완전한 제스처 지원** - 핀치 줌, 더블 탭 줌, 스와이프 네비게이션, 줌 상태에서의 팬, 세로 드래그로 닫기 지원
- 🏎️ **고성능 애니메이션** - React Native Reanimated 기반의 60fps 이상의 부드럽고 반응성 높은 애니메이션
- 🎨 **완전한 커스터마이징** - 컴포넌트, 스타일, 제스처 동작까지 완벽하게 제어 가능
- 🎛️ **외부 제어 API** - 버튼 등 다른 UI 컴포넌트에서 프로그래밍 방식으로 제어 가능
- 🧩 **다중 인스턴스 관리** - 고유 ID 기반으로 여러 뷰어를 독립적으로 관리
- 🧬 **유연한 통합** - Modal, [React Native Modal](https://www.npmjs.com/package/react-native-modal), ScrollView, FlatList, [FlashList](https://www.npmjs.com/package/@shopify/flash-list), [Expo Image](https://www.npmjs.com/package/expo-image), [FastImage](https://github.com/DylanVann/react-native-fast-image) 등 원하는 컴포넌트 사용
- 🧠 **완벽한 TypeScript 지원** - 타입 추론과 안정성을 갖춘 뛰어난 개발 경험 제공
- 🌐 **크로스 플랫폼 지원** - iOS, Android, Web에서 동작하며 Expo Go 및 New Architecture 지원
- 🪄 **간편한 API** - 복잡한 설정 없이도 직관적이고 쉽게 구현 가능
---
url: /2.x/ko/guide/getting-started/installation.md
---
> For AI agents: the complete documentation index is available at /2.x/ko/llms.txt, the full documentation bundle is available at /2.x/ko/llms-full.txt.
# 설치
:::warning React Native Reanimated v3 사용자
**React Native Reanimated v3**를 사용 중이라면, 최상의 경험을 위해 이 라이브러리의 **1.x 버전이 권장됩니다**.
👉 **[1.x 문서로 이동](/1.x/ko/guide/getting-started/installation.md)**
2.x 버전은 React Native Reanimated v4만 지원합니다.
:::
:::info 중요
`react-native-gesture-image-viewer`는 [`react-native-reanimated`](https://www.npmjs.com/package/react-native-reanimated)와 [`react-native-gesture-handler`](https://www.npmjs.com/package/react-native-gesture-handler)를 기반으로 한 높은 성능의 뷰어 라이브러리입니다.\
따라서 이 라이브러리를 사용하기 전에 React Native Reanimated와 Gesture Handler를 **반드시 설치**해야 합니다. 자세한 설정 가이드는 각 라이브러리의 공식 문서를 참고해주세요.
:::
#### 필수 요구사항
| 라이브러리 | 최소 버전 |
| :----------------------------- | :--------: |
| `react` | `>=18.0.0` |
| `react-native` | `>=0.75.0` |
| `react-native-gesture-handler` | `>=2.24.0` |
| `react-native-reanimated` | `>=4.0.0` |
| `react-native-worklets` | `>=0.5.0` |
## [React Native Reanimated 설정](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/)
### `react-native-reanimated` 및 `react-native-worklets` 설치:
```sh [npm]
npm install react-native-reanimated
```
```sh [yarn]
yarn add react-native-reanimated
```
```sh [pnpm]
pnpm add react-native-reanimated
```
```sh [bun]
bun add react-native-reanimated
```
```sh [deno]
deno add npm:react-native-reanimated
```
- `react-native-worklets`는 더 나은 모듈성을 위해 `react-native-reanimated`에서 분리되었으며 별도로 설치해야 합니다.
```sh [npm]
npm install react-native-worklets
```
```sh [yarn]
yarn add react-native-worklets
```
```sh [pnpm]
pnpm add react-native-worklets
```
```sh [bun]
bun add react-native-worklets
```
```sh [deno]
deno add npm:react-native-worklets
```
### Babel 설정
#### Expo
:::warning Expo Go 지원
Expo Go 환경에서 Reanimated 4를 사용하려면 [Expo SDK 54(beta)](https://expo.dev/go)를 사용해야 합니다.
:::
Expo를 사용할 경우 `expo prebuild`를 실행하여 ios와 android 디렉토리의 네이티브 코드를 업데이트합니다.
```sh [npm]
npm expo prebuild
```
```sh [yarn]
yarn expo prebuild
```
```sh [pnpm]
pnpm expo prebuild
```
```sh [bun]
bun expo prebuild
```
```sh [deno]
deno expo npm:prebuild
```
끝입니다! 이제 Expo 프로젝트에서 Reanimated 4가 구성되었습니다.
Expo SDK 50부터는 Expo 스타터 템플릿에 기본적으로 Reanimated Babel 플러그인이 포함되어 있습니다.
#### React Native CLI
React Native Community CLI를 사용할 때는 `babel.config.js`에 `react-native-worklets/plugin` 플러그인을 수동으로 추가해야 합니다.
```js title="babel.config.js"
module.exports = {
presets: [
... // don't add it here :)
],
plugins: [
...
// for web
'@babel/plugin-transform-export-namespace-from', // [!code highlight]
// react-native-worklets/plugin has to be listed last.
'react-native-worklets/plugin', // [!code highlight]
],
};
```
## [React Native Gesture Handler 설정](https://docs.swmansion.com/react-native-gesture-handler/docs/fundamentals/installation)
```sh [npm]
npm install react-native-gesture-handler
```
```sh [yarn]
yarn add react-native-gesture-handler
```
```sh [pnpm]
pnpm add react-native-gesture-handler
```
```sh [bun]
bun add react-native-gesture-handler
```
```sh [deno]
deno add npm:react-native-gesture-handler
```
- `react-native-gesture-handler`는 기본적으로 추가할 설정은 없지만, 공식 문서를 참고하여 환경에 맞게 설치해주세요.
- [안드로이드 환경의 모달에서 제스처를 사용](https://docs.swmansion.com/react-native-gesture-handler/docs/fundamentals/installation#android)하려면 모달의 콘텐츠를 `GestureHandlerRootView`로 래핑해야 정상적으로 동작하지만, **라이브러리 내부에 이미 `GestureHandlerRootView`가 래핑되어 있어 모달 사용 시 추가로 래핑할 필요가 없습니다.**
## React Native Gesture Image Viewer 설치
모든 설정이 끝났습니다! 🎉\
이제 `react-native-gesture-image-viewer`를 설치하면 됩니다.
```sh [npm]
npm install react-native-gesture-image-viewer@2
```
```sh [yarn]
yarn add react-native-gesture-image-viewer@2
```
```sh [pnpm]
pnpm add react-native-gesture-image-viewer@2
```
```sh [bun]
bun add react-native-gesture-image-viewer@2
```
```sh [deno]
deno add npm:react-native-gesture-image-viewer@2
```
---
url: /2.x/ko/guide/getting-started/quick-start.md
---
> For AI agents: the complete documentation index is available at /2.x/ko/llms.txt, the full documentation bundle is available at /2.x/ko/llms-full.txt.
# 빠른 시작
## 예제 및 데모
- [📁 예제 프로젝트](https://github.com/saseungmin/react-native-gesture-image-viewer/tree/main/example) - 실제 구현 코드와 다양한 사용 사례
- [🤖 Expo Snack](https://snack.expo.dev/@harang/react-native-gesture-image-viewer-v2) - Expo Snack에서 바로 체험
## 설치
:::warning 중요
`react-native-gesture-image-viewer`는 [`react-native-reanimated`](https://www.npmjs.com/package/react-native-reanimated)와 [`react-native-gesture-handler`](https://www.npmjs.com/package/react-native-gesture-handler)를 기반으로 한 높은 성능의 뷰어 라이브러리입니다.\
따라서 이 라이브러리를 사용하기 전에 React Native Reanimated와 Gesture Handler를 설치해야 합니다. **아직 설정을 하지 않았다면 [설치 가이드](/2.x/ko/guide/getting-started/installation.md)를 참고해주세요.**
:::
```sh [npm]
npm install react-native-gesture-image-viewer@2
```
```sh [yarn]
yarn add react-native-gesture-image-viewer@2
```
```sh [pnpm]
pnpm add react-native-gesture-image-viewer@2
```
```sh [bun]
bun add react-native-gesture-image-viewer@2
```
```sh [deno]
deno add npm:react-native-gesture-image-viewer@2
```
## 기본 사용법
`react-native-gesture-image-viewer`는 완전한 커스터마이징을 위해 제스처 동작에만 집중한 라이브러리입니다.
```tsx
import { useCallback, useState } from 'react';
import { ScrollView, Image, Modal, View, Text, Button, Pressable } from 'react-native';
import {
GestureViewer,
GestureTrigger,
useGestureViewerController,
useGestureViewerEvent,
useGestureViewerState,
} from 'react-native-gesture-image-viewer';
function App() {
const images = [...];
const [visible, setVisible] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(0);
const { goToIndex, goToPrevious, goToNext } = useGestureViewerController();
const { currentIndex, totalCount } = useGestureViewerState();
const openModal = (index: number) => {
setSelectedIndex(index);
setVisible(true);
};
const renderImage = useCallback((imageUrl: string) => {
return ;
}, []);
useGestureViewerEvent('zoomChange', (data) => {
console.log(`Zoom changed from ${data.previousScale} to ${data.scale}`);
});
return (
{images.map((uri, index) => (
openModal(index)}>
))}
setVisible(false)}
/>
);
}
```
:::tip `contain` 콘텐츠의 정확한 범위
정확한 줌과 팬 범위가 필요하다면 `getItemDimensions`로 콘텐츠의 원본 크기를 전달하거나,
이미지 로드 후 `setItemDimensions`로 등록하세요. 자세한 내용은
[콘텐츠 크기 가이드](/2.x/ko/guide/usage/item-dimensions.md)를 참고하세요.
:::
---
url: /2.x/ko/guide/getting-started/ai.md
---
> For AI agents: the complete documentation index is available at /2.x/ko/llms.txt, the full documentation bundle is available at /2.x/ko/llms-full.txt.
# AI
AI가 이 라이브러리의 기능, 버전별 문서, 프로젝트 규칙을 더 잘 이해해서 개발이나 트러블슈팅 과정에서 더 정확한 도움을 줄 수 있도록, 이 프로젝트는 다음 기능들을 제공합니다.
## llms.txt
[`llms.txt`](https://llmstxt.org/)는 LLM이 프로젝트 문서를 더 쉽게 탐색하고 활용할 수 있도록 돕는 표준입니다. 이 사이트는 다음 파일들을 제공합니다.
- [llms.txt](https://react-native-gesture-image-viewer.pages.dev/2.x/ko/llms.txt): `2.x` 문서용 구조화된 인덱스 파일
```txt
https://react-native-gesture-image-viewer.pages.dev/2.x/ko/llms.txt
```
- [llms-full.txt](https://react-native-gesture-image-viewer.pages.dev/2.x/ko/llms-full.txt): `2.x` 문서 전체를 하나로 합친 파일
```txt
https://react-native-gesture-image-viewer.pages.dev/2.x/ko/llms-full.txt
```
사용 목적에 따라 적절한 파일을 선택하면 됩니다.
- `llms.txt`는 더 작고 가벼워서 필요한 문서만 선택적으로 가져올 때 적합합니다.
- `llms-full.txt`는 해당 버전 문서 전체를 한 번에 제공하므로, 넓은 범위의 이해나 큰 리팩터링 작업에 적합합니다.
## Markdown docs
모든 문서 페이지는 AI에게 직접 전달할 수 있는 Markdown 버전도 함께 제공합니다.
예시:
```txt
https://react-native-gesture-image-viewer.pages.dev/2.x/ko/guide/getting-started/overview.md
https://react-native-gesture-image-viewer.pages.dev/2.x/ko/guide/getting-started/quick-start.md
https://react-native-gesture-image-viewer.pages.dev/2.x/ko/guide/usage/programmatic-control.md
```
특정 기능 하나만 물어볼 때는 전체 문서보다 관련 Markdown 페이지 하나를 제공하는 편이 더 효율적입니다.
---
url: /2.x/ko/guide/migration-from-1.x.md
---
> For AI agents: the complete documentation index is available at /2.x/ko/llms.txt, the full documentation bundle is available at /2.x/ko/llms-full.txt.
# 1.x에서 2.x로 마이그레이션
## [Reanimated 3.x에서 4.x로 마이그레이션](https://docs.swmansion.com/react-native-reanimated/docs/guides/migration-from-3.x)
### 마이그레이션 단계
### `react-native-reanimated` 패키지 업그레이드:
```sh [npm]
npm install react-native-reanimated
```
```sh [yarn]
yarn add react-native-reanimated
```
```sh [pnpm]
pnpm add react-native-reanimated
```
```sh [bun]
bun add react-native-reanimated
```
```sh [deno]
deno add npm:react-native-reanimated
```
### `react-native-worklets` 패키지 설치:
```sh [npm]
npm install react-native-worklets
```
```sh [yarn]
yarn add react-native-worklets
```
```sh [pnpm]
pnpm add react-native-worklets
```
```sh [bun]
bun add react-native-worklets
```
```sh [deno]
deno add npm:react-native-worklets
```
### 설정 업데이트
#### Expo
`ios` 및 `android` 디렉토리의 네이티브 코드를 업데이트하기 위해 prebuild를 실행합니다.
```sh [npm]
npm expo prebuild
```
```sh [yarn]
yarn expo prebuild
```
```sh [pnpm]
pnpm expo prebuild
```
```sh [bun]
bun expo prebuild
```
```sh [deno]
deno expo npm:prebuild
```
끝입니다! 이제 Expo 프로젝트에서 Reanimated 4가 구성되었습니다.
Expo SDK 50부터는 Expo 스타터 템플릿에 기본적으로 Reanimated Babel 플러그인이 포함되어 있습니다.
#### React Native CLI
React Native Community CLI를 사용할 때는 `babel.config.js`에 `react-native-worklets/plugin` 플러그인을 수동으로 추가해야 합니다.
```diff title="babel.config.js"
module.exports = {
presets: [
... // 여기에 추가하지 마세요 :)
],
plugins: [
...
// for web
'@babel/plugin-transform-export-namespace-from', // [!code highlight]
- 'react-native-reanimated/plugin',
+ 'react-native-worklets/plugin',
],
};
```
`metro.config.js`에서 `wrapWithReanimatedMetroConfig`를 제거합니다.
```diff title="metro.config.js"
- const { wrapWithReanimatedMetroConfig } = require('react-native-reanimated/metro-config');
const config = {
// 기존 Metro 설정 옵션들
};
- module.exports = wrapWithReanimatedMetroConfig(config);
+ module.exports = config
```
## `useGestureViewerController`에서 `currentIndex`와 `totalCount` 제거
:::info ❗ 호환성 변경사항
- `useGestureViewerController`는 더 이상 `currentIndex`와 `totalCount`를 반환하지 않습니다. 대신 `useGestureViewerState`를 사용하세요.
- `GestureViewerControllerState` 타입을 `GestureViewerState`로 이름을 변경합니다.
:::
```diff
import {
GestureViewer,
- GestureViewerControllerState,
+ GestureViewerState
useGestureViewerController,
useGestureViewerEvent,
+ useGestureViewerState,
} from 'react-native-gesture-image-viewer';
const {
goToIndex, goToPrevious, goToNext, zoomIn, zoomOut, resetZoom, rotate,
- currentIndex, totalCount
} = useGestureViewerController();
+ const { currentIndex, totalCount } = useGestureViewerState();
```
## `onIndexChange` prop 제거
:::info ❗ 호환성 변경사항
`onIndexChange` prop이 제거되었습니다. 대신 `useGestureViewerState`와 `useEffect`를 사용하세요.
:::
```tsx
// 이전
console.log(index)} />;
// 이후
const { currentIndex } = useGestureViewerState();
useEffect(() => {
console.log(currentIndex);
}, [currentIndex]);
```
## 제스처 prop 이름 변경
더 나은 개발자 경험을 위해 제스처 prop 이름을 개선했습니다.
:::info ❗ 호환성 변경사항
- `enableDismissGesture` → `dismiss.enabled`
- `dismissThreshold` → `dismiss.threshold`
- `resistance` → `dismiss.resistance`
- `animateBackdrop` → `dismiss.fadeBackdrop`
- `useSnap` → `enableSnapMode`
- `enableZoomPanGesture` → `enablePanWhenZoomed`
- `enableZoomGesture` → `enablePinchZoom`
- `enableSwipeGesture` → `enableHorizontalSwipe`
:::
```diff
```
---
url: /2.x/ko/guide/performance-optimization-tips.md
---
> For AI agents: the complete documentation index is available at /2.x/ko/llms.txt, the full documentation bundle is available at /2.x/ko/llms-full.txt.
# 성능 최적화 팁
#### `renderItem` 함수는 `useCallback`으로 감싸서 불필요한 리렌더링을 방지하세요.
```tsx
import { useCallback, useState } from 'react'; // [!code highlight]
import { ScrollView, Image, Modal } from 'react-native';
import { GestureViewer } from 'react-native-gesture-image-viewer';
const images = [...];
function App() {
const [visible, setVisible] = useState(false);
// [!code highlight:3]
const renderImage = useCallback((imageUrl: string) => {
return ;
}, []);
return (
setVisible(false)}>
setVisible(false)}
/>
);
}
```
#### 변경되지 않은 `data`를 다시 만들지 마세요.
목록 내용이 바뀔 때 새 배열을 전달하는 것은 올바른 사용법이며 정상적으로 지원됩니다. 다만 부모 컴포넌트가 자주 rerender될 때 동일한 배열을 매번 다시 만들면 viewer와 치수 조정 작업이 반복될 수 있습니다. 변환이 필요하지 않다면 기존 배열을 그대로 전달하고, 실제 파생 데이터만 메모이제이션하세요.
```tsx
import { useMemo } from 'react';
// ❌ 지양: 렌더링할 때마다 새 배열을 만듭니다.
// ✅ 권장: 내용이 바뀌지 않았다면 기존 prop 또는 state 값을 그대로 사용합니다.
// ✅ 변환이 필요하다면 원본 값을 기준으로 파생 배열을 메모이제이션합니다.
function LimitedGallery({ images }: { images: string[] }) {
const visibleImages = useMemo(
() => images.slice(0, 20),
[images],
);
return ;
}
```
`data`를 제자리에서 변경하지 말고, 내용이 바뀔 때 배열을 교체하세요.
```tsx
// ❌ 지양
images.push(nextImage);
setImages(images);
// ✅ 권장
setImages((currentImages) => [...currentImages, nextImage]);
```
#### 대용량 이미지의 경우 [`expo-image`](https://docs.expo.dev/versions/latest/sdk/image/)나 [`FastImage`](https://github.com/DylanVann/react-native-fast-image) 사용을 권장합니다.
```tsx
import { useCallback, useState } from 'react';
import { ScrollView, Modal } from 'react-native';
import { GestureViewer } from 'react-native-gesture-image-viewer';
import { Image } from 'expo-image'; // [!code highlight]
const images = [...];
function App() {
const [visible, setVisible] = useState(false);
const renderImage = useCallback((imageUrl: string) => {
return (
); // [!code highlight]
}, []);
return (
setVisible(false)}>
setVisible(false)}
/>
);
}
```
#### 많은 수의 이미지를 다룰 때는 [`FlashList`](https://shopify.github.io/flash-list/) 사용을 권장합니다.
- [FlashList Performance 가이드](https://shopify.github.io/flash-list/docs/fundamentals/performance)
```tsx
import { useCallback, useState } from 'react';
import { Image, Modal } from 'react-native';
import { GestureViewer } from 'react-native-gesture-image-viewer';
import { FlashList } from '@shopify/flash-list'; // [!code highlight]
const images = [...];
function App() {
const [visible, setVisible] = useState(false);
const renderImage = useCallback((imageUrl: string) => {
return ;
}, []);
return (
setVisible(false)}>
setVisible(false)}
/>
);
}
```
#### 디바이스에서 테스트하세요. (시뮬레이터에서는 성능이 제한될 수 있습니다.)
---
url: /2.x/ko/guide/usage/basic-usage.md
---
> For AI agents: the complete documentation index is available at /2.x/ko/llms.txt, the full documentation bundle is available at /2.x/ko/llms-full.txt.
# 기본 사용법
## GestureViewer
```tsx
import { useCallback, useState } from 'react';
import { FlatList, Image, Modal } from 'react-native';
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
const images = [...];
const [visible, setVisible] = useState(false);
const renderImage = useCallback((imageUrl: string) => {
return ;
}, []);
return (
setVisible(false)}>
setVisible(false)}
/>
);
}
```
:::tip `contain` 콘텐츠의 정확한 범위
정확한 줌과 팬 범위가 필요하다면 `getItemDimensions`로 콘텐츠의 원본 크기를 전달하거나,
이미지 로드 후 `setItemDimensions`로 등록하세요. 자세한 내용은
[콘텐츠 크기 가이드](/2.x/ko/guide/usage/item-dimensions.md)를 참고하세요.
:::
## [useGestureViewerController](/2.x/ko/guide/usage/programmatic-control.md)
`useGestureViewerController` 훅을 사용하여 `GestureViewer`를 프로그래밍 방식으로 제어할 수 있습니다.
```tsx
import { GestureViewer, useGestureViewerController } from 'react-native-gesture-image-viewer';
function App() {
const { goToIndex, goToPrevious, goToNext, zoomIn, zoomOut, resetZoom, rotate } =
useGestureViewerController();
return (
{/* Navigation Controls */}
goToIndex(2)} />
);
}
```
## [useGestureViewerState](/2.x/ko/guide/usage/gesture-viewer-state.md)
`useGestureViewerState` 훅을 사용하여 `GestureViewer`의 현재 상태를 가져올 수 있습니다.
```tsx
import { GestureViewer, useGestureViewerState } from 'react-native-gesture-image-viewer';
function App() {
const { currentIndex, totalCount } = useGestureViewerState();
return (
{`${currentIndex + 1} / ${totalCount}`}
);
}
```
## [useGestureViewerEvent](/2.x/ko/guide/usage/handling-viewer-events.md)
`useGestureViewerEvent` 훅을 사용하여 `GestureViewer`의 특정 이벤트를 구독할 수 있습니다. 줌이나 회전과 같은 실시간 제스처 변화에 반응할 수 있습니다.
```tsx
import { GestureViewer, useGestureViewerEvent } from 'react-native-gesture-image-viewer';
function App() {
useGestureViewerEvent('zoomChange', (data) => {
console.log(`Zoom changed from ${data.previousScale} to ${data.scale}`);
});
useGestureViewerEvent('rotationChange', (data) => {
console.log(`Rotation changed from ${data.previousRotation}° to ${data.rotation}°`);
});
useGestureViewerEvent('tap', (data) => {
if (data.kind === 'single') {
console.log(`Tapped item ${data.index} at (${data.x}, ${data.y})`);
}
});
return ;
}
```
## [GestureTrigger](/2.x/ko/guide/usage/trigger-based-animations.md)
`GestureTrigger`는 썸네일과 같은 트리거 요소에서 전체 모달 뷰로의 원활한 전환을 생성하는 부드러운 트리거 기반 애니메이션을 지원합니다. 이 기능은 트리거와 모달 콘텐츠 간의 시각적 연속성을 유지하여 사용자 경험을 향상시킵니다.
```tsx
import { GestureTrigger, GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
const [visible, setVisible] = useState(false);
return (
setVisible(true)}>
setVisible(false)} />
);
}
```
---
url: /2.x/ko/guide/usage/custom-components.md
---
> For AI agents: the complete documentation index is available at /2.x/ko/llms.txt, the full documentation bundle is available at /2.x/ko/llms-full.txt.
# 커스텀 컴포넌트
`react-native-gesture-image-viewer`는 강력한 기능으로 완벽한 컴포넌트 커스터마이징이 가능합니다. 이미지뿐만 아니라 원하는 컴포넌트로 제스처를 지원하는 아이템을 만들 수 있습니다.
## 모달 컴포넌트
다음과 같이 원하는 `Modal`을 사용하여 뷰어를 만들 수 있습니다.
**Use Modal**
```tsx
import { FlatList, Image, Modal } from 'react-native';
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
const images = [...];
const [visible, setVisible] = useState(false);
return (
setVisible(false)}>
setVisible(false)}
/>
);
}
```
**Use react-nativee-modal**
```tsx
import { FlatList, Image } from 'react-native';
import Modal from 'react-native-modal';
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
const images = [...];
const [visible, setVisible] = useState(false);
return (
setVisible(false)}
onBackdropPress={() => setVisible(false)}
hasBackdrop={false}
style={styles.modal}
useNativeDriver={true}
hideModalContentWhileAnimating={true}
animationInTiming={300}
animationOutTiming={300}
>
setVisible(false)}
/>
);
}
```
## 리스트 컴포넌트
`ListComponent` props를 통해 `ScrollView`, `FlatList`, [`FlashList`](https://shopify.github.io/flash-list/) 등 원하는 리스트를 지원합니다.\
`listProps`는 **선택한 리스트 컴포넌트에 맞는 타입 추론**을 제공하여, IDE에서 정확한 자동완성과 타입 안전성을 보장합니다.
```tsx
import { FlashList } from '@shopify/flash-list';
function App() {
return (
);
}
```
## 콘텐츠 컴포넌트
`renderItem` props를 통해 [`expo-image`](https://docs.expo.dev/versions/latest/sdk/image/), [`FastImage`](https://github.com/DylanVann/react-native-fast-image) 등 다양한 종류의 콘텐츠 컴포넌트를 주입하여 제스처를 사용할 수 있습니다.
```tsx
import { useCallback } from 'react';
import { Image } from 'expo-image';
import { ScrollView } from 'react-native';
import { GestureViewer } from 'react-native-gesture-image-viewer';
const images = [
'https://images.unsplash.com/photo-1682687220742-aba13b6e50ba',
'https://images.unsplash.com/photo-1682687220063-4742bd7fd538',
'https://images.unsplash.com/photo-1682687218147-9806132dc697',
];
function App() {
const renderImage = useCallback((imageUrl: string) => {
return (
);
}, []);
return ;
}
```
`contain` 콘텐츠의 정확한 줌과 팬 범위가 필요하다면 [콘텐츠 크기 가이드](/2.x/ko/guide/usage/item-dimensions.md)를 참고하세요.
### `renderItem` 활성 상태 (`isActive`)
비디오처럼 현재 표시 중인 콘텐츠에서만 재생이나 작업을 수행해야 한다면 세 번째 `renderItem` 인자의 `isActive`를 사용할 수 있습니다. 이 값은 현재 선택된 콘텐츠에서만 `true`입니다. 페이지 전환 중에는 기존 콘텐츠가 활성 상태를 유지하고, 이동이 완료되면 새로 선택된 콘텐츠가 활성화됩니다. 아이템 외부 UI에서 전역 `currentIndex`가 필요할 때는 [`useGestureViewerState`](/2.x/ko/guide/usage/gesture-viewer-state.md)를 사용하세요.
```tsx
import type { GestureViewerRenderItemInfo } from 'react-native-gesture-image-viewer';
function renderMedia(item: MediaItem, _index: number, { isActive }: GestureViewerRenderItemInfo) {
return (
);
}
return ;
```
---
url: /2.x/ko/guide/usage/gesture-features.md
---
> For AI agents: the complete documentation index is available at /2.x/ko/llms.txt, the full documentation bundle is available at /2.x/ko/llms-full.txt.
# 제스처 기능
`react-native-gesture-image-viewer`는 뷰어에 필요한 다양한 제스처를 지원합니다. 제스처 동작의 기본값은 아래 예제를 참고해주세요.
```tsx
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
return (
);
}
```
## Gesture Props
### `dismiss`
어떤 수직 스와이프 방향에서 `onDismiss`를 호출할지 제어하는 제스처 옵션입니다. 기본값은 기존 동작과 동일한 아래 방향 dismiss이며, 모달 스타일 닫기 제스처에 유용합니다.
| 속성 | 설명 | 타입 | 기본값 |
| :------------- | :----------------------------------------------------------------- | :------------------- | :----: |
| `enabled` | `false`일 때 dismiss 제스처가 비활성화됩니다. | `boolean` | `true` |
| `direction` | dismiss를 트리거할 수직 스와이프 방향을 설정합니다. | `down`, `up`, `both` | `down` |
| `threshold` | `threshold`는 수직 제스처 중에 임계값을 적용하여 `onDismiss`가 호출되는 시점을 제어합니다. | `number` | `80` |
| `resistance` | `resistance`는 dismiss 제스처 중에 저항을 적용하여 수직 이동 범위를 제어합니다. | `number` | `2` |
| `fadeBackdrop` | 기본적으로 설정된 dismiss 방향으로 드래그하는 동안 배경 `opacity`가 1에서 0으로 점진적으로 감소합니다. | `boolean` | `true` |
### `enableHorizontalSwipe` (기본값: `true`)
좌우 스와이프 제스처를 제어합니다. `false`일 때 가로 제스처가 비활성화됩니다.
### `enablePinchZoom` (기본값: `true`)
두 손가락 핀치 제스처를 제어합니다. `false`일 때 핀치 줌 제스처가 비활성화됩니다. 핀치 줌은 두 손가락 사이의 중심점을 기준으로 확대됩니다.
### `enableDoubleTapZoom` (기본값: `true`)
더블탭 줌 제스처를 제어합니다. `false`일 때 더블탭 줌 제스처가 비활성화됩니다. 더블탭 줌은 탭한 위치를 중심으로 확대됩니다.
### `enablePanWhenZoomed` (기본값: `true`)
줌이 활성화된 상태에서만 작동하며, 확대된 상태에서 아이템 위치를 이동할 수 있게 합니다. `false`일 때 줌 상태에서의 제스처 이동이 비활성화됩니다. 콘텐츠가 화면 밖으로 나가지 않도록 자동으로 경계를 제한합니다.
---
url: /2.x/ko/guide/usage/gesture-viewer-props.md
---
> For AI agents: the complete documentation index is available at /2.x/ko/llms.txt, the full documentation bundle is available at /2.x/ko/llms-full.txt.
# GestureViewer Props
### `renderItem`
`renderItem`은 `data`의 각 아이템을 렌더링하며, `renderItem(item, index, { isActive, setItemDimensions })` 형태로 세 번째 인자에서 `isActive`와 `setItemDimensions`를 제공합니다.
비디오 재생처럼 활성 아이템에서만 필요한 작업은 `isActive`로 제어할 수 있습니다. 동작 방식과 예시는 [`renderItem` 활성 상태 (`isActive`)](/2.x/ko/guide/usage/custom-components.md#renderitem-active-state)를 참고하세요.
`setItemDimensions({ width, height })`는 렌더링된 콘텐츠의 원본 크기를 알 수 있게 된 뒤 호출하세요. 예시와 호출 시점은 [콘텐츠 크기 가이드](/2.x/ko/guide/usage/item-dimensions.md)를 참고하세요.
### `enableLoop` (기본값: `false`)
루프 모드를 활성화합니다. `true`일 때 마지막 아이템에서 다음으로 가면 첫 번째로, 첫 번째에서 이전으로 가면 마지막으로 돌아갑니다.
```tsx
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
return (
);
}
```
### `enableSnapMode` (기본값: `false`)
스냅 스크롤 모드를 활성화합니다.
- **`false` (기본값)**: 페이징 모드 (`pagingEnabled: true`)
- 전체 화면 크기 단위로 스크롤됩니다
- **`true`**: 스냅 모드 (`snapToInterval` 자동 계산)
- `snapToInterval`이 `width`와 `itemSpacing` 값을 기반으로 자동 계산됩니다
- 아이템 간격이 필요할 때 이 옵션을 사용하세요
```tsx
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
return (
);
}
```
### `itemSpacing` (기본값: `0`)
아이템 간의 간격을 픽셀 단위로 설정합니다. `enableSnapMode`가 `true`일 때만 적용됩니다.
```tsx
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
return (
);
}
```
### `autoPlay` (기본값: `false`)
자동 재생 모드를 활성화합니다. `true`일 때 지정된 간격 후 다음 아이템으로 자동으로 재생됩니다.
- `enableLoop`이 활성화되면 마지막 아이템에서 다음으로 가면 첫 번째로, 첫 번째에서 이전으로 가면 마지막으로 돌아갑니다.
- `enableLoop`이 비활성화되면 마지막 아이템에서 정지됩니다.
- 아이템이 하나만 있을 때 자동 재생이 비활성화됩니다.
- 줌 또는 회전 제스처가 감지되면 자동 재생이 일시 중지됩니다.
```tsx
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
return (
);
}
```
### `autoPlayInterval` (기본값: `3000`)
자동 재생 간격을 밀리초 단위로 설정합니다.\
양수 정수여야 합니다. 값이 250ms 미만이면 런타임에서 250ms로 제한됩니다.
```tsx
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
return (
);
}
```
### `onSingleTap`
뷰어에서 싱글 탭이 확정되면 실행됩니다. 헤더, 푸터, 캡션, 액션 버튼처럼 뷰어 컨트롤 UI를 토글할 때 유용하며, 뷰어 위에 별도의 pressable을 덮지 않아도 됩니다.
- 더블 탭 줌이 활성화되어 있으면 더블 탭이 아닌지 확인한 뒤 실행되므로 약간 늦게 호출될 수 있습니다
- 스와이프, 핀치, dismiss, 더블 탭 줌 제스처에서는 호출되지 않습니다
```tsx
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
const [showControls, setShowControls] = useState(true);
return (
setShowControls((prev) => !prev)} // [!code highlight]
/>
);
}
```
### `getItemDimensions`
콘텐츠의 원본 크기를 이미 알고 있을 때 반환합니다. `renderItem`에서 `setItemDimensions`로 런타임에 등록한 크기가 우선합니다. 예시는 [콘텐츠 크기 가이드](/2.x/ko/guide/usage/item-dimensions.md)를 참고하세요.
### `getItemKey`
같은 내용이 같은 슬롯에서 다른 객체로 다시 만들어질 수 있을 때 사용할 안정적인 논리 키를 반환합니다. `setItemDimensions`로 런타임에 등록한 크기와 함께 사용하세요. 자세한 내용은 [콘텐츠 크기 가이드](/2.x/ko/guide/usage/item-dimensions.md#getitemkey)를 참고하세요.
### `initialIndex` (기본값: `0`)
뷰어가 표시할 아이템 인덱스를 설정할 수 있습니다. 이 값은 현재 `data` 길이에 맞게 정규화됩니다. 유한하지 않은 값과 음수는 `0`이 되고, 범위를 초과한 값은 마지막 인덱스가 되며, 빈 데이터는 `0`으로 처리됩니다.
뷰어가 이미 마운트된 뒤 `initialIndex`가 바뀌면 정규화된 인덱스로 다시 이동합니다.
### `maxZoomScale` (기본값: `2`)
최대 줌 배율을 제어할 수 있습니다.
## `GestureViewerProps` interface
[Go to source](https://github.com/saseungmin/react-native-gesture-image-viewer/blob/f03e1b9/src/types.ts#L85)
````tsx title="GestureViewerProps"
export type GestureViewerItemDimensions = Readonly<{
/** 콘텐츠의 원본 너비입니다. 유한한 양수여야 합니다. */
width: number;
/** 콘텐츠의 원본 높이입니다. 유한한 양수여야 합니다. */
height: number;
}>;
export type GestureViewerItemDimensionsResolver = (
item: ItemT,
index: number,
) => GestureViewerItemDimensions | undefined;
export type GestureViewerItemKey = string | number;
export type GestureViewerItemKeyResolver = (
item: ItemT,
index: number,
) => GestureViewerItemKey;
export type GestureViewerRenderItemInfo = {
/**
* 렌더링된 아이템이 현재 활성 상태인지 여부입니다.
* @remarks 페이지 전환 중에는 기존 아이템이 활성 상태를 유지합니다. 전환이 완료되어 다른 아이템으로 이동하면 해당 아이템이 활성화됩니다.
*/
readonly isActive: boolean;
/**
* 렌더링된 콘텐츠의 원본 크기를 사용할 수 있게 된 후 등록합니다.
* @remarks 이미지 로드 또는 이벤트 콜백에서 호출하거나, 커밋 이후의 일반 `useEffect`에서 호출하세요. 렌더링 중이나 하위 컴포넌트의 `useLayoutEffect`에서는 호출하지 마세요.
*/
readonly setItemDimensions: (dimensions: GestureViewerItemDimensions) => void;
};
export interface GestureViewerProps {
/**
* 여러 개의 `GestureViewer` 인스턴스를 효율적으로 관리하고 싶다면 `id` prop을 사용해 각각의 `GestureViewer`를 구분할 수 있습니다.
* @remarks `GestureViewer`는 컴포넌트가 언마운트되면 인스턴스를 메모리에서 자동으로 제거하므로 별도의 수동 메모리 관리가 필요하지 않습니다.
* @defaultValue 'default'
*/
id?: string;
/**
* `GestureViewer`에 표시할 데이터입니다.
*/
data: ItemT[];
/**
* `GestureViewer`에 표시할 아이템의 인덱스입니다.
* @remarks
* - 현재 data 길이에 맞게 정규화됩니다. 유한하지 않은 값과 음수는 0, 범위를 초과한 값은 마지막 인덱스, 빈 데이터는 0이 됩니다.
* - prop이 바뀌면 이미 마운트된 viewer도 정규화된 인덱스로 다시 이동합니다.
* @defaultValue 0
*/
initialIndex?: number;
/**
* `GestureViewer`가 닫힐 때 호출되는 콜백 함수입니다.
*/
onDismiss?: () => void;
/**
* dismiss 인터랙션이 시작될 때 호출되는 콜백 함수입니다.
* @remarks dismiss 제스처/애니메이션이 진행되는 동안 외부 UI(예: 헤더, 버튼)를 숨길 때 유용합니다.
*/
onDismissStart?: () => void;
/**
* 아이템을 렌더링할 때 호출되는 콜백 함수입니다.
* @param item - 렌더링할 아이템입니다.
* @param index - 렌더링된 아이템의 리스트 인덱스입니다.
* @param info - 이 리스트 셀의 렌더링 상태입니다.
*/
renderItem: (item: ItemT, index: number, info: GestureViewerRenderItemInfo) => React.ReactElement;
/**
* 뷰어 콘텐츠에서 싱글 탭이 확정되었을 때 호출되는 콜백 함수입니다.
* @remarks
* - 이 콜백은 탭이 싱글 탭으로 확정된 뒤에만 실행되므로, 더블 탭 줌이 활성화되어 있으면 약간 지연될 수 있습니다.
* - 스와이프, 핀치, dismiss, 더블 탭 줌 제스처에서는 호출되지 않습니다.
* - 전체 화면 탭 처리가 필요하다면 `renderContainer`에서 pressable을 덮어씌우는 대신 이 콜백 사용을 권장합니다.
*/
onSingleTap?: (event: GestureViewerSingleTapEvent) => void;
/**
* 콘텐츠의 원본 크기를 이미 알고 있을 때 반환합니다.
* @remarks 크기를 아직 모르면 `undefined`를 반환하세요. 유효하지 않은 크기는 뷰어 셀 크기로 대체됩니다.
*/
getItemDimensions?: GestureViewerItemDimensionsResolver;
/**
* 같은 아이템이 같은 슬롯에서 다른 객체로 다시 만들어질 수 있을 때 사용할 안정적인 논리 키를 반환합니다.
* @remarks 객체 동일성이 리렌더링 사이에 유지되지 않을 때 사용하세요. 이 키는 같은 렌더 결과를 가리켜야 하며, 이미지 소스나 원본 크기가 바뀌면 함께 바뀌어야 합니다. 배열 인덱스만 그대로 반환하지 마세요. 아이템이 다른 위치로 이동하면 새 슬롯에서 크기를 다시 등록할 때까지 `getItemDimensions` 또는 뷰어 셀 크기를 사용합니다.
*/
getItemKey?: GestureViewerItemKeyResolver;
/**
* 컨테이너를 렌더링할 때 호출되는 콜백 함수입니다.
* @remarks 뷰어 주변에 추가 UI(예: 닫기 버튼, 툴바)를 구성할 때 유용합니다.
* 전체 화면 탭 처리는 뷰어 콘텐츠 위에 pressable을 덮어씌우기보다 `onSingleTap` 사용을 권장합니다.
* 두 번째 인자는 `dismiss()`처럼 뷰어를 닫을 수 있는 제어 헬퍼를 제공합니다.
*
* @param children - 컨테이너 내부에 렌더링할 뷰어 콘텐츠입니다.
* @param helpers - 뷰어 제어 헬퍼입니다. 현재는 `dismiss()`를 포함합니다.
* @returns 전달된 `children`을 감싸서 렌더링하는 React 엘리먼트입니다.
*/
renderContainer?: (
children: React.ReactElement,
helpers: { dismiss: () => void },
) => React.ReactElement;
/**
* `ListComponent` prop을 통해 `ScrollView`, `FlatList`, `FlashList` 같은 다양한 리스트 컴포넌트를 지원합니다.
*/
ListComponent: LC;
/**
* `GestureViewer`의 너비입니다.
* @remarks 이 prop을 지정하지 않으면 `GestureViewer`의 너비는 화면 너비와 동일해집니다.
* @defaultValue 화면 너비
*/
width?: number;
/**
* `GestureViewer`의 높이입니다.
* @remarks 이 prop을 지정하지 않으면 `GestureViewer`의 높이는 화면 높이와 동일해집니다.
* @defaultValue 화면 높이
*/
height?: number;
/**
* 리스트 컴포넌트에 전달할 props입니다.
* @remarks `listProps`는 **선택한 리스트 컴포넌트를 기준으로 타입 추론**을 제공하므로 IDE에서 더 정확한 자동완성과 타입 안정성을 보장합니다.
*/
listProps?: Partial>;
/**
* backdrop의 스타일입니다.
*/
backdropStyle?: StyleProp;
/**
* 컨테이너의 스타일입니다.
*/
containerStyle?: StyleProp;
/**
* 자동 재생 모드입니다.
* @remarks
* - `true`이면 지정된 간격 후 다음 아이템이 자동으로 재생됩니다.
* - `enableLoop`가 활성화되어 있으면 마지막 아이템 다음에 첫 번째 아이템으로 돌아갑니다.
* - `enableLoop`가 비활성화되어 있으면 마지막 아이템에서 멈춥니다.
* - 아이템이 하나뿐이면 자동 재생은 비활성화됩니다.
* - 줌 또는 회전 제스처가 감지되면 자동 재생이 일시 정지됩니다.
* @defaultValue false
*/
autoPlay?: boolean;
/**
* 자동 재생 간격입니다.
* @remarks
* - `autoPlay`가 활성화되어 있으면 지정된 간격(ms)마다 다음 아이템으로 이동합니다.
* - 양의 정수여야 합니다. 250ms 미만 값은 런타임에서 250ms로 보정됩니다.
* @defaultValue 3000
*/
autoPlayInterval?: number;
/**
* dismiss 제스처 옵션입니다.
* @remarks 위/아래 방향을 선택할 수 있는 수직 스와이프 닫기 제스처에 유용합니다.
*/
dismiss?: {
/**
* `false`이면 dismiss 제스처가 비활성화됩니다.
* @defaultValue true
*/
enabled?: boolean;
/**
* 어떤 수직 스와이프 방향에서 `onDismiss`를 호출할지 설정합니다.
* @remarks 기존 동작과 호환되는 `down`, 위로만 닫는 `up`, 양방향 모두 허용하는 `both`를 사용할 수 있습니다.
* @defaultValue 'down'
*/
direction?: GestureViewerDismissDirection;
/**
* `threshold`는 수직 제스처 중 임계값을 적용해 `onDismiss`가 호출되는 시점을 제어합니다.
* @defaultValue 80
*/
threshold?: number;
/**
* `resistance`는 dismiss 제스처 중 저항을 적용해 수직 이동 범위를 제어합니다.
* @defaultValue 2
*/
resistance?: number;
/**
* 기본적으로 설정된 dismiss 방향으로 드래그하는 동안 배경 `opacity`가 점진적으로 감소합니다.
* @remarks `false`이면 이 애니메이션이 비활성화됩니다.
* @defaultValue true
*/
fadeBackdrop?: boolean;
};
/**
* 좌우 스와이프 제스처를 제어합니다.
* @remarks `false`이면 가로 제스처가 비활성화됩니다.
* @defaultValue true
*/
enableHorizontalSwipe?: boolean;
/**
* 줌이 활성화된 경우에만 동작하며, 확대된 상태에서 아이템 위치를 이동할 수 있게 합니다.
* @remarks `false`이면 줌 중 제스처 이동이 비활성화됩니다.
* @defaultValue true
*/
enablePanWhenZoomed?: boolean;
/**
* 두 손가락 핀치 제스처를 제어합니다.
* @remarks `false`이면 두 손가락 줌 제스처가 비활성화됩니다.
* @defaultValue true
*/
enablePinchZoom?: boolean;
/**
* 더블 탭 줌 제스처를 제어합니다.
* @remarks `false`이면 더블 탭 줌 제스처가 비활성화됩니다.
* @defaultValue true
*/
enableDoubleTapZoom?: boolean;
/**
* 무한 루프 탐색을 활성화합니다.
* @defaultValue false
*/
enableLoop?: boolean;
/**
* 스냅 스크롤 모드를 활성화합니다.
*
* @remarks
* **`false` (기본값)**: 페이징 모드 (`pagingEnabled: true`)
* - 화면 전체 크기 단위로 스크롤됩니다.
*
* **`true`**: 스냅 모드 (`snapToInterval` 자동 계산)
* - `snapToInterval`은 `width`와 `itemSpacing` 값을 기준으로 자동 계산됩니다.
* - 아이템 간격이 필요할 때 이 옵션을 사용하세요.
* @defaultValue false
*
*/
enableSnapMode?: boolean;
/**
* 아이템 사이 간격(픽셀)입니다.
* @remarks `enableSnapMode`가 `true`일 때만 적용됩니다.
* @defaultValue 0
*/
itemSpacing?: number;
/**
* 최대 줌 배율입니다.
* @defaultValue 2
*/
maxZoomScale?: number;
/**
* 트리거 기반 애니메이션 설정입니다.
* @remarks 애니메이션 지속 시간, easing, 시스템 reduce-motion 동작을 커스터마이즈할 수 있습니다.
*
* @example
* ```tsx
* {
* console.log('애니메이션 완료');
* },
* }}
* />
* ```
*/
triggerAnimation?: TriggerAnimationConfig;
}
````
---
url: /2.x/ko/guide/usage/gesture-viewer-state.md
---
> For AI agents: the complete documentation index is available at /2.x/ko/llms.txt, the full documentation bundle is available at /2.x/ko/llms-full.txt.
# GestureViewer 상태 추적
`GestureViewer`는 뷰어의 현재 상태를 추적하는 상태 관리 시스템을 제공합니다. `useGestureViewerState` 훅을 사용하여 뷰어의 상태를 접근할 수 있습니다.
## useGestureViewerState
```tsx
import { GestureViewer, useGestureViewerState } from 'react-native-gesture-image-viewer';
function App() {
const { currentIndex, totalCount } = useGestureViewerState();
return (
{`${currentIndex + 1} / ${totalCount}`}
);
}
```
### 매개변수
- `id?: string`
- `GestureViewer` 인스턴스의 ID입니다.
- 기본값: `"default"`
### API Reference
| 속성 | 설명 | 타입 | 기본값 |
| :------------- | :---------------------- | :------- | :-: |
| `currentIndex` | 현재 표시되고 있는 아이템의 인덱스입니다. | `number` | `0` |
| `totalCount` | 전체 아이템의 개수입니다. | `number` | `0` |
---
url: /2.x/ko/guide/usage/handling-viewer-events.md
---
> For AI agents: the complete documentation index is available at /2.x/ko/llms.txt, the full documentation bundle is available at /2.x/ko/llms-full.txt.
# 뷰어 이벤트 처리하기
`useGestureViewerEvent` 훅을 사용하여 `GestureViewer`의 특정 이벤트를 구독할 수 있습니다. 줌, 회전, 탭과 같은 뷰어 상호작용에 반응할 수 있습니다.
## useGestureViewerEvent
```tsx
import { GestureViewer, useGestureViewerEvent } from 'react-native-gesture-image-viewer';
function App() {
useGestureViewerEvent('zoomChange', (data) => {
console.log(`줌 변경: ${data.previousScale} → ${data.scale}`);
});
useGestureViewerEvent('rotationChange', (data) => {
console.log(`회전 변경: ${data.previousRotation}° → ${data.rotation}°`);
});
useGestureViewerEvent('tap', (data) => {
if (data.kind === 'single') {
console.log(`아이템 ${data.index} 탭: (${data.x}, ${data.y})`);
}
});
return ;
}
```
### 특정 인스턴스의 이벤트 구독
`useGestureViewerEvent` 훅의 첫 번째 인수로 인스턴스 ID를 전달하여 특정 인스턴스의 이벤트를 구독할 수 있습니다.
```ts
// 특정 인스턴스의 이벤트 구독
useGestureViewerEvent('gallery', 'zoomChange', (data) => {
console.log(`갤러리 줌: ${data.scale}x`);
});
```
:::note
기본 `id` 값은 `default`입니다.
:::
### API Reference
```ts
// 기본 인스턴스의 이벤트 구독
function useGestureViewerEvent(
eventType: T,
callback: GestureViewerEventCallback,
): void;
// 특정 인스턴스의 이벤트 구독
function useGestureViewerEvent(
instanceId: string,
eventType: T,
callback: GestureViewerEventCallback,
): void;
```
| 이벤트 타입 | 설명 | 콜백 데이터 |
| :--------------- | :----------------------------------------- | :-------------------------------------------------------- |
| `zoomChange` | 핀치 제스처 중 줌 스케일이 변경될 때 발생 | `{ scale: number, previousScale: number }` |
| `rotationChange` | 회전 제스처 중 회전 각도가 변경될 때 발생 | `{ rotation: number, previousRotation: number }` |
| `tap` | 뷰어에서 탭이 확정되면 발생합니다. 현재는 확정된 싱글 탭만 emit합니다. | `{ kind: 'single', x: number, y: number, index: number }` |
:::tip
`tap` 이벤트를 `GestureViewer` prop로 직접 처리하고 싶다면 [`onSingleTap`](/2.x/ko/guide/usage/gesture-viewer-props.md#onsingletap)을 사용할 수 있습니다.
:::
---
url: /2.x/ko/guide/usage/item-dimensions.md
---
> For AI agents: the complete documentation index is available at /2.x/ko/llms.txt, the full documentation bundle is available at /2.x/ko/llms-full.txt.
# 콘텐츠 크기
`GestureViewer`는 렌더된 콘텐츠의 원본 크기를 알 때 더 정확한 줌과 팬 범위를 계산할 수 있습니다.
이 값은 특히 `contain` 이미지에서 중요합니다. 콘텐츠 크기를 제공하지 않으면 기존 동작처럼 전체 뷰어 셀을 콘텐츠 크기로 사용합니다.
원본 크기를 화면에 맞춘 뒤, 콘텐츠가 화면보다 작은 축에서는 중앙에 유지하고 큰 축에서는 콘텐츠 가장자리까지만 이동할 수 있습니다.
| 상황 | 사용할 API |
| ---------------------------- | --------------------------------- |
| `data`에 원본 크기가 이미 있음 | `getItemDimensions` |
| 로드 후 원본 크기를 알 수 있음 | `setItemDimensions` |
| 리렌더링 사이에 아이템 객체가 다시 생성될 수 있음 | `setItemDimensions`와 `getItemKey` |
| 크기를 제공하지 않음 | 뷰어 셀 크기를 사용 |
`width`와 `height`는 모두 0보다 큰 유한한 숫자여야 합니다.
## `getItemDimensions`
각 아이템에 원본 `width`와 `height` 값이 이미 있다면 `getItemDimensions`를 사용하세요.
```tsx
import { ScrollView } from 'react-native';
import { GestureViewer } from 'react-native-gesture-image-viewer';
import { Image } from 'expo-image';
const images = [
{ uri: 'https://picsum.photos/400/200', width: 400, height: 200 },
{ uri: 'https://picsum.photos/200/400', width: 200, height: 400 },
];
function renderImage(image: (typeof images)[number]) {
return (
);
}
function getImageDimensions(image: (typeof images)[number]) {
return { width: image.width, height: image.height };
}
function App() {
return (
);
}
```
크기를 아직 모르면 `undefined`를 반환해도 됩니다.
## `setItemDimensions`
이미지 컴포넌트가 로드 이후 원본 크기를 제공한다면 세 번째 `renderItem` 인자의 `setItemDimensions`를 사용하세요.
이미지 로드 또는 이벤트 콜백에서 호출하거나, 커밋 이후의 일반 `useEffect`에서 호출하세요. 렌더링 중이나 하위 컴포넌트의 `useLayoutEffect`에서는 호출하지 마세요.
`setItemDimensions`로 런타임에 등록한 크기는 `getItemDimensions`보다 우선합니다.
### React Native Image
```tsx
import { Image } from 'react-native';
import type { GestureViewerRenderItemInfo } from 'react-native-gesture-image-viewer';
function renderImage(
image: { uri: string },
_index: number,
{ setItemDimensions }: GestureViewerRenderItemInfo,
) {
return (
{
setItemDimensions({
width: nativeEvent.source.width,
height: nativeEvent.source.height,
});
}}
/>
);
}
```
### Expo Image
```tsx
import { Image } from 'expo-image';
import type { GestureViewerRenderItemInfo } from 'react-native-gesture-image-viewer';
function renderImage(
image: { uri: string },
_index: number,
{ setItemDimensions }: GestureViewerRenderItemInfo,
) {
return (
{
setItemDimensions({
width: source.width,
height: source.height,
});
}}
/>
);
}
```
### FastImage
```tsx
import FastImage from 'react-native-fast-image';
import type { GestureViewerRenderItemInfo } from 'react-native-gesture-image-viewer';
function renderImage(
image: { uri: string },
_index: number,
{ setItemDimensions }: GestureViewerRenderItemInfo,
) {
return (
{
setItemDimensions({
width: nativeEvent.width,
height: nativeEvent.height,
});
}}
/>
);
}
```
## `getItemKey`
`setItemDimensions`를 사용할 때 같은 논리 아이템이 같은 슬롯에서 다른 객체로 다시 만들어질 수 있다면 `getItemKey`를 함께 사용하세요.
```tsx
image.uri}
ListComponent={ScrollView}
/>
```
키는 렌더링되는 콘텐츠를 식별해야 합니다. 이미지 소스나 원본 크기가 바뀌면 키도 함께 바뀌어야 합니다. 배열 인덱스만 그대로 반환하지 마세요.
런타임에 등록한 크기는 같은 슬롯의 동일한 아이템에만 유지됩니다. 아이템이 다른 인덱스로 이동하면 해당 슬롯에서 크기를 다시 제공해야 합니다.
## 크기를 제공하지 않은 경우
`getItemDimensions`와 `setItemDimensions` 모두 유효한 크기를 제공하지 않으면 뷰어는 전체 뷰어 셀을 콘텐츠 크기로 사용합니다. 기존 동작과 호환되지만, `contain` 콘텐츠는 실제 보이는 가장자리보다 더 멀리 이동할 수 있습니다.
---
url: /2.x/ko/guide/usage/multi-instance-management.md
---
> For AI agents: the complete documentation index is available at /2.x/ko/llms.txt, the full documentation bundle is available at /2.x/ko/llms-full.txt.
# 다중 인스턴스 관리
여러 개의 `GestureViewer` 인스턴스를 효율적으로 관리하고 싶은 경우 `id` 값을 적용하면 여러 개의 `GestureViewer`를 사용할 수 있습니다.
`GestureViewer`는 컴포넌트가 언마운트되면 메모리에서 자동으로 인스턴스가 제거되어 메모리 관리를 수동으로 할 필요가 없습니다.
:::note
`id` 기본값은 `default`입니다.
:::
```tsx
import {
GestureViewer,
GestureTrigger,
useGestureViewerController,
useGestureViewerEvent,
useGestureViewerState,
} from 'react-native-gesture-image-viewer';
const firstViewerId = 'firstViewerId';
const secondViewerId = 'secondViewerId';
function App() {
const { goToIndex: goToFirstIndex } = useGestureViewerController(firstViewerId);
const { goToIndex: goToSecondIndex } = useGestureViewerController(secondViewerId);
const { currentIndex: firstCurrentIndex } = useGestureViewerState(firstViewerId);
const { currentIndex: secondCurrentIndex } = useGestureViewerState(secondViewerId);
useGestureViewerEvent(firstViewerId, 'zoomChange', (data) => {
console.log(`Gallery zoom: ${data.scale}x`);
});
useGestureViewerEvent(secondViewerId, 'zoomChange', (data) => {
console.log(`Gallery zoom: ${data.scale}x`);
});
return (
goToFirstIndex(2)} />
goToSecondIndex(0)} />
);
}
```
---
url: /2.x/ko/guide/usage/programmatic-control.md
---
> For AI agents: the complete documentation index is available at /2.x/ko/llms.txt, the full documentation bundle is available at /2.x/ko/llms-full.txt.
# 프로그래밍 방식 제어
`useGestureViewerController` 훅을 사용하여 `GestureViewer`를 프로그래밍 방식으로 제어할 수 있습니다.
## useGestureViewerController
```tsx
import { GestureViewer, useGestureViewerController } from 'react-native-gesture-image-viewer';
function App() {
const { goToIndex, goToPrevious, goToNext, zoomIn, zoomOut, resetZoom, rotate } =
useGestureViewerController();
return (
{/* Zoom Controls & Rotation Controls */}
zoomIn(0.25)} />
zoomOut(0.25)} />
{
rotate(0);
resetZoom();
}}
/>
rotate(90)} />
rotate(90, false)} />
{/* Navigation Controls */}
goToIndex(2)} />
);
}
```
### 매개변수
- `id?: string`
- `GestureViewer` 인스턴스의 ID입니다.
- 기본값: `"default"`
## API Reference
| 속성 | 설명 | 타입 | 파라미터 기본값 |
| :------------- | :------------------ | :----------------------------------------------------- | :--------: |
| `goToIndex` | 특정 인덱스로 이동합니다. | `(index: number) => void` | - |
| `goToPrevious` | 이전 아이템으로 이동합니다. | `() => void` | - |
| `goToNext` | 다음 아이템으로 이동합니다. | `() => void` | - |
| `zoomIn` | 지정된 배수만큼 확대합니다. | `(multiplier?: number) => void` | `0.25` |
| `zoomOut` | 지정된 배수만큼 축소합니다. | `(multiplier?: number) => void` | `0.25` |
| `resetZoom` | 지정된 스케일로 줌을 초기화합니다. | `(scale?: number) => void` | `1` |
| `rotate` | 지정된 각도만큼 회전합니다. | `(angle?: RotationAngle, clockwise?: boolean) => void` | `90, true` |
- `zoomIn(multiplier?)`
- **multiplier**: 확대할 배수 (범위: `0.01 ~ 1`)
- 예: `zoomIn(0.5)` → 현재 스케일의 50% 만큼 추가 확대
- `zoomOut(multiplier?)`
- **multiplier**: 축소할 배수 (범위: `0.01 ~ 1`)
- 예: `zoomOut(0.3)` → 현재 스케일을 1.3으로 나누어 축소
- `resetZoom(scale?)`
- **scale**: 초기화할 스케일 값
- 예: `resetZoom(1.5)` → 1.5배 스케일로 초기화
- `rotate(angle?, clockwise?)`
- **angle**: 회전할 각도 (0, 90, 180, 270, 360)
- **clockwise**: 회전 방향 (true: 시계방향, false: 반시계방향)
- 예: `rotate(90)` → 90도 시계방향 회전
- 예: `rotate(90, false)` → 90도 반시계방향 회전
- 예: `rotate(0)` → 회전 초기화
---
url: /2.x/ko/guide/usage/style-customization.md
---
> For AI agents: the complete documentation index is available at /2.x/ko/llms.txt, the full documentation bundle is available at /2.x/ko/llms-full.txt.
# 스타일 커스터마이징
`GestureViewer`의 스타일을 커스터마이징할 수 있습니다.
```tsx
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
return (
{children}}
/>
);
}
```
| 속성 | 설명 | 기본값 |
| :----------------------------------: | :------------------------------------------------- | :------------------------------------------------: |
| `width` | 콘텐츠 아이템의 너비입니다. 기본값은 화면 너비입니다. | `Dimensions width` |
| `height` | 콘텐츠 아이템의 높이입니다. 기본값은 화면 높이입니다. | `Dimensions height` |
| `containerStyle` | 리스트 컴포넌트를 감싸는 컨테이너의 커스텀 스타일을 지정할 수 있습니다. | `flex: 1` |
| `backdropStyle` | 뷰어의 배경 스타일을 커스터마이징할 수 있습니다. | `backgroundColor: black; StyleSheet.absoluteFill;` |
| `renderContainer(children, helpers)` | ``를 레핑하는 커스텀 래퍼 컴포넌트를 지정할 수 있습니다. | |
:::tip
전체 화면 탭 처리는 `onSingleTap` 사용을 권장합니다. `renderContainer`는 헤더, 닫기 버튼, 툴바처럼 주변 UI를 구성할 때 가장 잘 맞습니다.
:::
---
url: /2.x/ko/guide/usage/trigger-based-animations.md
---
> For AI agents: the complete documentation index is available at /2.x/ko/llms.txt, the full documentation bundle is available at /2.x/ko/llms-full.txt.
# 트리거 기반 모달 애니메이션
`GestureTrigger`는 썸네일과 같은 트리거 요소에서 전체 모달 뷰로의 원활한 전환을 생성하는 부드러운 트리거 기반 애니메이션을 지원합니다. 이 기능은 트리거와 모달 콘텐츠 간의 시각적 연속성을 유지하여 사용자 경험을 향상시킵니다.
## GestureTrigger 사용법
`GestureTrigger` 컴포넌트는 누를 수 있는 요소를 감싸고 부드러운 모달 전환을 위해 해당 위치를 등록합니다.
```tsx
import { GestureTrigger, GestureViewer } from 'react-native-gesture-image-viewer';
function Gallery() {
const [visible, setVisible] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(0);
const openModal = (index: number) => {
setSelectedIndex(index);
setVisible(true);
};
return (
{/* 갤러리 그리드 */}
{images.map((uri, index) => (
openModal(index)}>
))}
{/* 모달 */}
setVisible(false)}
triggerAnimation={{
duration: 300,
easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
onAnimationComplete: () => console.log('애니메이션 완료!'),
}}
/>
);
}
```
### 1. GestureTrigger에 onPress 사용 (권장)
```tsx
openModal(index)}>
```
### 2. 하위 요소에 onPress 사용
```tsx
openModal(index)}>
```
:::note 중요
- 두 방법은 기능적으로 동일합니다
- `GestureTrigger`는 자동으로 하위 컴포넌트에 press 핸들러를 주입합니다
- 하위 컴포넌트는 반드시 press 가능해야 합니다 (`onPress` prop 지원)
- `GestureTrigger`와 하위 요소 모두에 `onPress`가 있는 경우, 둘 다 호출됩니다 (하위 요소의 핸들러가 먼저 호출됨)
**지원되는 하위 컴포넌트**
다음과 같은 press 가능한 컴포넌트를 하위 요소로 사용할 수 있습니다:
- `Pressable`
- `TouchableOpacity`
- `TouchableHighlight`
- `Button`
- `onPress` prop를 가지고 있는 컴포넌트 및 커스텀 컴포넌트
**다양한 컴포넌트 예시**
```tsx
// TouchableOpacity 사용
이미지 보기;
// 커스텀 컴포넌트 사용
function CustomButton({ onPress, children }) {
return {children};
}
커스텀 버튼;
```
:::
## 애니메이션 구성
`triggerAnimation` prop을 사용하여 트리거 애니메이션 동작을 사용자 정의할 수 있습니다:
```tsx
import { ReduceMotion, Easing } from 'react-native-reanimated';
function App() {
return (
{
// 애니메이션 완료 시 콜백
console.log('모달 애니메이션 완료');
},
}}
/>
);
}
```
## 다중 트리거 인스턴스
다른 ID를 사용하여 여러 트리거 인스턴스를 가질 수 있습니다:
```tsx
// 사진 갤러리 트리거
openPhotoModal(index)}>
// 비디오 갤러리 트리거
openVideoModal(index)}>
```
:::tip
애니메이션이 제대로 작동하려면 `GestureTrigger`와 `GestureViewer` 컴포넌트 간에 `id` prop이 일치해야 합니다. (기본값: `default`)
현재 보고 있는 아이템의 썸네일 위치로 닫기 애니메이션을 되돌리고 싶다면, 각 `GestureTrigger`에 `index={index}`를 함께 전달하세요.
:::
### 현재 인덱스 기준 닫기 애니메이션
갤러리처럼 여러 썸네일이 있고 모달 안에서 좌우 스와이프로 다른 아이템으로 이동할 수 있는 경우, `GestureTrigger`에 `index`를 전달하면 닫을 때 **현재 보고 있는 아이템의 트리거 위치**를 우선 사용합니다.
```tsx
{
images.map((uri, index) => (
openModal(index)}>
));
}
```
`index`를 전달하지 않으면 닫기 애니메이션은 기본적으로 **처음 열 때 사용한 트리거 위치**를 기준으로 동작합니다.
:::note fallback 동작
현재 인덱스에 해당하는 트리거를 찾을 수 없는 경우(예: 리스트 virtualization으로 썸네일이 unmount된 경우), 닫기 애니메이션은 다음 순서로 fallback 됩니다:
1. 현재 오픈을 시작한 트리거 위치
2. 트리거를 찾을 수 없으면 일반 dismiss
:::
## 닫기 애니메이션 처리
### onDismissStart 콜백
`onDismissStart` 콜백은 닫기 애니메이션이 시작될 때 즉시 트리거되며, 애니메이션이 완료되기 전에 사라져야 하는 UI 요소를 숨기는 데 유용합니다.
```tsx
function App() {
const [visible, setVisible] = useState(false);
const [showExternalUI, setShowExternalUI] = useState(false);
const handleDismissStart = () => {
setShowExternalUI(false);
};
return (
{showExternalUI && (
{`${currentIndex + 1} / ${totalCount}`}
)}
);
}
```
### 커스텀 컴포넌트에서 닫기
`renderContainer`의 `dismiss` 헬퍼를 사용하여 프로그래밍 방식으로 뷰어를 닫을 수 있습니다:
```tsx
(
{children}
닫기
)}
/>
```
:::note `renderContainer`의 `dismiss`를 사용하는 이유
트리거 기반 애니메이션을 사용할 때는 `setVisible(false)`로 직접 제어하는 대신 `renderContainer`가 제공하는 `dismiss` 메서드를 사용하는 것이 중요합니다. 그 이유는 다음과 같습니다:
```tsx
// ❌ 피하세요: 트리거 애니메이션 없이 즉시 닫힙니다
setVisible(false)} title="닫기" />
// ✅ 권장: 트리거 애니메이션을 사용하여 닫힙니다
```
**작동 방식:**
1. 트리거 애니메이션 사용 시:
- `dismiss`가 호출되면 뷰어가 원래 트리거 위치로 애니메이션되며 돌아갑니다
- 애니메이션 시작 시 `onDismissStart`가 호출됩니다
- 애니메이션 완료 후 `onDismiss`가 호출됩니다
2. 트리거 애니메이션 없이 사용 시:
- 트리거 애니메이션이 구성되지 않은 경우에도 `dismiss`는 간단한 닫기로 작동합니다
:::
### 닫기 처리 완전한 예제
```tsx
function ImageViewer() {
const [visible, setVisible] = useState(false);
const [showUI, setShowUI] = useState(true);
return (
setShowUI(false)}
onDismiss={() => setVisible(false)}
renderContainer={(children, { dismiss }) => (
{children}
{showUI && (
)}
)}
/>
);
}
```
### 트리거 애니메이션과 함께하는 닫기 동작
트리거 기반 애니메이션을 사용할 때, 닫기 애니메이션은 원래 트리거 위치로 돌아갑니다. `onDismissStart` 콜백은 이 애니메이션이 시작될 때 호출되므로, 닫기 애니메이션 중에 표시되지 않아야 하는 UI 요소를 숨길 수 있습니다.
```tsx
{
console.log('닫기 애니메이션 시작됨');
setShowUI(false);
}}
onDismiss={() => {
console.log('닫기 애니메이션 완료됨');
setVisible(false);
}}
/>
```
이 패턴은 다음과 같은 사용자 경험을 보장합니다:
1. 닫기가 시작되면 즉시 UI 요소를 숨깁니다
2. 닫기 애니메이션이 자연스럽게 완료되도록 합니다
3. 애니메이션이 완전히 완료된 후에만 리소스를 정리합니다
### 모범 사례
1. 애니메이션과 함께 뷰어를 닫으려면 항상 `renderContainer`의 `dismiss` 메서드를 사용하세요
2. 닫기 애니메이션 중에 표시되지 않아야 하는 UI 요소는 `onDismissStart`를 사용하여 숨기세요
3. 애니메이션 완료 후에 실행되어야 하는 정리 작업은 `onDismiss`에 배치하세요
#### 흔히 하는 실수
```tsx
// ❌ 피하세요: 트리거 애니메이션을 우회합니다
setVisible(false)} title="닫기" />
// ❌ 피하세요: 애니메이션이 중단됩니다
const handleClose = () => {
setShowUI(false);
setVisible(false); // 너무 일찍 닫힙니다
};
// ✅ 올바른 방법: 애니메이션이 자연스럽게 완료되도록 합니다
setShowUI(false)}
onDismiss={() => setVisible(false)}
renderContainer={(children, { dismiss }) => (
{children}
{showUI && (
)}
)}
/>
```
이 패턴은 트리거 기반 애니메이션이 일관되게 작동하도록 하며 최상의 사용자 경험을 제공합니다.
## API 참조
### GestureTrigger Props
```ts
interface GestureTriggerProps {
id?: string; // GestureViewer과 연결할 식별자 (기본값: "default")
index?: number; // 현재 인덱스 기준 dismiss 애니메이션을 위한 아이템 인덱스
children: ReactElement; // 단일 pressable 자식 요소
onPress?: (...args: unknown[]) => void; // 추가 onPress 핸들러
}
```
### TriggerAnimation 구성
```ts
interface TriggerAnimationConfig {
duration?: number; // 밀리초 단위의 애니메이션 지속 시간
easing?: EasingFunction; // 사용자 정의 이징 함수
reduceMotion?: boolean; // 시스템 감소 모션 설정 준수 여부
onAnimationComplete?: () => void; // 애니메이션 완료 시 호출될 콜백
}
```
| 속성 | 타입 | 기본값 | 설명 |
| :-------------------- | :--------------- | :------------------------------------ | :------------------- |
| `duration` | `number` | `300` | 밀리초 단위의 애니메이션 지속 시간 |
| `easing` | `EasingFunction` | `Easing.bezier(0.25, 0.1, 0.25, 1.0)` | 애니메이션을 위한 이징 함수 |
| `reduceMotion` | `ReduceMotion` | `undefined` | 시스템 감소 모션 설정 준수 여부 |
| `onAnimationComplete` | `() => void` | `undefined` | 애니메이션이 완료될 때 호출되는 콜백 |
:::note
트리거 애니메이션은 누를 때 트리거 요소의 위치를 측정하고 해당 위치에서 전체 화면으로 모달을 애니메이션하는 방식으로 작동합니다.
:::
---
url: /2.x/ko/index.md
---
> For AI agents: the complete documentation index is available at /2.x/ko/llms.txt, the full documentation bundle is available at /2.x/ko/llms-full.txt.
# React Native Gesture Image Viewer
부드럽고 유연한 이미지 뷰어
> Reanimated 기반의 이미지 제스처와 완전한 제어
[빠른 시작](/ko/guide/getting-started/quick-start.html) | [GitHub](https://github.com/saseungmin/react-native-gesture-image-viewer)
## Features
- 🤌 **완전한 제스처 지원**: 핀치 줌, 더블 탭 줌, 스와이프 네비게이션, 줌 상태에서의 팬, 세로 드래그로 닫기 지원
- 🏎️ **고성능 애니메이션**: React Native Reanimated 기반의 60fps 이상의 부드럽고 반응성 높은 애니메이션
- 🎨 **완전한 커스터마이징**: 컴포넌트, 스타일, 제스처 동작까지 완벽하게 제어 가능
- 🎛️ **외부 제어 API**: 버튼 등 다른 UI 컴포넌트에서 프로그래밍 방식으로 제어 가능
- 🧩 **다중 인스턴스 관리**: 고유 ID 기반으로 여러 뷰어를 독립적으로 관리
- 🧬 **유연한 통합**: Modal, FlatList, FlashList, Expo Image, FastImage 등과 매끄럽게 연동
- 🧠 **완벽한 TypeScript 지원**: 타입 추론과 안정성을 갖춘 뛰어난 개발 경험 제공
- 🌐 **크로스 플랫폼 지원**: iOS, Android, Web에서 동작하며 Expo Go 및 New Architecture 지원
- 🪄 **간편한 API**: 복잡한 설정 없이도 직관적이고 쉽게 구현 가능