---
url: /3.x-beta/ko/guide/getting-started/overview.md
---
# 개요
이 문서는 v3 beta 문서입니다. v3가 stable로 릴리스되기 전까지 기본 stable 문서는 2.x로 유지됩니다.
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/)를 기반으로 한 고성능 **범용 제스처 뷰어** 라이브러리로, 이미지뿐만 아니라 비디오, 커스텀 컴포넌트 등 모든 콘텐츠에 완전한 커스터마이징과 직관적인 제스처 지원을 제공합니다.
## v3 페이징 동작 방식
3.x는 더 이상 사용자가 넘긴 `ScrollView`, `FlatList`, `FlashList`에 의존해서 페이징하지 않습니다. `GestureViewer`가 제스처 기반 render window로 가로 페이징을 내부에서 직접 처리합니다.
`windowSize`는 현재 아이템 주변에 유지할 render window의 크기를 결정하며 기본값은 `3`입니다. 기본 설정에서는 이전, 현재, 다음 페이지를 마운트합니다. non-loop의 데이터 경계에서는 범위를 벗어난 페이지를 생략합니다.
애니메이션으로 페이지를 전환하는 동안 Reanimated가 기존 페이지들을 먼저 이동합니다. 전환이 끝나면 새로운 현재 아이템을 기준으로 render window를 갱신합니다. 이때 계속 범위 안에 있는 페이지는 마운트 상태를 유지하고, 범위를 벗어난 페이지는 언마운트하며, 새로 인접한 페이지를 마운트합니다.

`enableLoop`가 켜져 있으면 내부 render window가 마지막 아이템에서 첫 번째 아이템으로, 첫 번째 아이템에서 마지막 아이템으로 이어집니다. 이 동작도 리스트 구현에 의존하지 않습니다.
인접 콘텐츠를 더 많이 마운트해야 할 때만 `windowSize`를 늘리고, 페이지 사이에 보이는 간격이 필요할 때는 `pageSpacing`을 사용하세요.
## 주요 특징
- 🤌 **완전한 제스처 지원** - 핀치 줌, 더블 탭 줌, 스와이프 네비게이션, 줌 상태에서의 팬, 세로 드래그로 닫기 지원
- 🏎️ **고성능 애니메이션** - React Native Reanimated 기반의 60fps 이상의 부드럽고 반응성 높은 애니메이션
- 🪟 **리스트 없는 render window** - `ScrollView`, `FlatList`, `FlashList` 없이 내부 제스처 기반 페이징 처리
- 🎨 **완전한 커스터마이징** - 컴포넌트, 스타일, 제스처 동작까지 완벽하게 제어 가능
- 🎛️ **외부 제어 API** - 버튼 등 다른 UI 컴포넌트에서 프로그래밍 방식으로 제어 가능
- 🧩 **다중 인스턴스 관리** - 고유 ID 기반으로 여러 뷰어를 독립적으로 관리
- 🧬 **유연한 통합** - Modal, [React Native Modal](https://www.npmjs.com/package/react-native-modal), [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: /3.x-beta/ko/guide/getting-started/installation.md
---
# 설치
:::warning React Native Reanimated v3 사용자
**React Native Reanimated v3**를 사용 중이라면, 최상의 경험을 위해 이 라이브러리의 **1.x 버전이 권장됩니다**.
👉 **[1.x 문서로 이동](/1.x/ko/guide/getting-started/installation.md)**
3.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`의 beta 릴리스를 설치하면 됩니다.
```sh [npm]
npm install react-native-gesture-image-viewer@beta
```
```sh [yarn]
yarn add react-native-gesture-image-viewer@beta
```
```sh [pnpm]
pnpm add react-native-gesture-image-viewer@beta
```
```sh [bun]
bun add react-native-gesture-image-viewer@beta
```
```sh [deno]
deno add npm:react-native-gesture-image-viewer@beta
```
---
url: /3.x-beta/ko/guide/getting-started/quick-start.md
---
# 빠른 시작
## 예제 및 데모
- [📁 예제 프로젝트](https://github.com/saseungmin/react-native-gesture-image-viewer/tree/main/example) - 실제 구현 코드와 다양한 사용 사례
- [🤖 Expo Snack](https://snack.expo.dev/@harang/react-native-gesture-image-viewer-v3) - 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를 설치해야 합니다. **아직 설정을 하지 않았다면 [설치 가이드](/3.x-beta/ko/guide/getting-started/installation.md)를 참고해주세요.**
:::
```sh [npm]
npm install react-native-gesture-image-viewer@beta
```
```sh [yarn]
yarn add react-native-gesture-image-viewer@beta
```
```sh [pnpm]
pnpm add react-native-gesture-image-viewer@beta
```
```sh [bun]
bun add react-native-gesture-image-viewer@beta
```
```sh [deno]
deno add npm:react-native-gesture-image-viewer@beta
```
## 기본 사용법
`react-native-gesture-image-viewer`는 완전한 커스터마이징을 위해 제스처 동작에만 집중한 라이브러리입니다.
```tsx
import { useCallback, useState } from 'react';
import { 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)}
/>
);
}
```
---
url: /3.x-beta/ko/guide/getting-started/ai.md
---
# AI
AI가 이 라이브러리의 기능, 버전별 문서, 프로젝트 규칙을 더 잘 이해해서 개발이나 트러블슈팅 과정에서 더 정확한 도움을 줄 수 있도록, 이 프로젝트는 다음 기능들을 제공합니다.
## llms.txt
[`llms.txt`](https://llmstxt.org/)는 LLM이 프로젝트 문서를 더 쉽게 탐색하고 활용할 수 있도록 돕는 표준입니다. 이 사이트는 다음 파일들을 제공합니다.
- [llms.txt](https://react-native-gesture-image-viewer.pages.dev/3.x-beta/ko/llms.txt): `3.x-beta` 문서용 구조화된 인덱스 파일
```txt
https://react-native-gesture-image-viewer.pages.dev/3.x-beta/ko/llms.txt
```
- [llms-full.txt](https://react-native-gesture-image-viewer.pages.dev/3.x-beta/ko/llms-full.txt): `3.x-beta` 문서 전체를 하나로 합친 파일
```txt
https://react-native-gesture-image-viewer.pages.dev/3.x-beta/ko/llms-full.txt
```
사용 목적에 따라 적절한 파일을 선택하면 됩니다.
- `llms.txt`는 더 작고 가벼워서 필요한 문서만 선택적으로 가져올 때 적합합니다.
- `llms-full.txt`는 해당 버전 문서 전체를 한 번에 제공하므로, 넓은 범위의 이해나 큰 리팩터링 작업에 적합합니다.
## Markdown docs
모든 문서 페이지는 AI에게 직접 전달할 수 있는 Markdown 버전도 함께 제공합니다.
예시:
```txt
https://react-native-gesture-image-viewer.pages.dev/3.x-beta/ko/guide/getting-started/overview.md
https://react-native-gesture-image-viewer.pages.dev/3.x-beta/ko/guide/getting-started/quick-start.md
https://react-native-gesture-image-viewer.pages.dev/3.x-beta/ko/guide/usage/programmatic-control.md
```
특정 기능 하나만 물어볼 때는 전체 문서보다 관련 Markdown 페이지 하나를 제공하는 편이 더 효율적입니다.
---
url: /3.x-beta/ko/guide/migration-from-2.x.md
---
# 2.x에서 3.x로 마이그레이션
3.x에서는 사용자가 넘기던 리스트 기반 페이징 레이어를 제거했습니다. 이제 `GestureViewer`가 내부 제스처 기반 render window로 페이징을 직접 처리하므로, 뷰어에 `ScrollView`, `FlatList`, `FlashList`를 넘길 필요가 없습니다.
## 마이그레이션 단계
### 리스트 컴포넌트 props 제거
`GestureViewer`에서 `ListComponent`와 `listProps`를 제거하세요.
```diff
item.id,
- }}
/>
```
### snap 간격을 pageSpacing으로 교체
`enableSnapMode`와 `itemSpacing`은 제거되었습니다. 페이지 사이에 보이는 간격이 필요하다면 [`pageSpacing`](/3.x-beta/ko/guide/usage/gesture-viewer-props.md#pagespacing)을 사용하세요.
```diff
```
### 필요한 경우에만 render window 조정
3.x의 기본값은 이전, 현재, 다음 총 세 페이지를 마운트합니다. 인접한 콘텐츠를 더 많이 마운트해야 할 때만 [`windowSize`](/3.x-beta/ko/guide/usage/gesture-viewer-props.md#windowsize)를 늘리세요.
```tsx
```
### 프로그래밍 방식 이동 유지
[`goToIndex`, `goToPrevious`, `goToNext` controller 메서드](/3.x-beta/ko/guide/usage/programmatic-control.md)는 계속 동작합니다. [`horizontalSwipe`](/3.x-beta/ko/guide/usage/gesture-features.md#horizontalswipe)에서 `enabled: false`를 설정하면 사용자/마우스 가로 스와이프 제스처만 비활성화되며, controller 이동과 autoplay는 계속 사용할 수 있습니다.
```diff
```
```tsx
const { goToIndex } = useGestureViewerController();
goToIndex(2);
goToIndex(2, { animated: false });
```
## 제거된 Props
| 2.x prop | 3.x 대체 방식 |
| ---------------- | -------------------------------------------------------------------------------------- |
| `ListComponent` | 제거됨. 페이징은 내부에서 처리됩니다. |
| `listProps` | 제거됨. 콘텐츠 커스터마이징은 [`renderItem`](/3.x-beta/ko/guide/usage/custom-components.md)을 사용하세요. |
| `enableSnapMode` | 제거됨. 내부 페이징은 항상 제스처 기반입니다. |
| `itemSpacing` | [`pageSpacing`](/3.x-beta/ko/guide/usage/gesture-viewer-props.md#pagespacing) |
## 자주 쓰던 `listProps` 마이그레이션
3.x는 더 이상 `ScrollView`, `FlatList`, `FlashList`로 props를 전달하지 않습니다. 자주 쓰던 `listProps` 설정은 아래 v3 API로 옮기세요.
| 2.x `listProps` 사용 | 3.x 방향 |
| ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `keyExtractor` | 제거됨. render window가 slot key를 직접 관리합니다. 아이템 식별성은 `data`에서 안정적으로 유지하고, 콘텐츠는 `renderItem`에서 렌더링하세요. |
| `initialScrollIndex` | [`initialIndex`](/3.x-beta/ko/guide/usage/gesture-viewer-props.md#initialindex)를 사용하세요. |
| `scrollEnabled`, `pagingEnabled`, `horizontal`, `snapToInterval`, `decelerationRate` | 제거됨. 페이징은 내부 제스처가 소유합니다. 사용자 스와이프를 잠글 때는 [`horizontalSwipe`](/3.x-beta/ko/guide/usage/gesture-features.md#horizontalswipe)에서 `enabled: false`를 설정하고, 페이지 사이 간격은 [`pageSpacing`](/3.x-beta/ko/guide/usage/gesture-viewer-props.md#pagespacing)을 사용하세요. |
| `onScroll`, `onMomentumScrollEnd`, `onViewableItemsChanged` | 제거됨. `currentIndex`/`totalCount`는 [`useGestureViewerState`](/3.x-beta/ko/guide/usage/gesture-viewer-state.md)를 사용하고, 지원되는 뷰어 인터랙션은 [viewer event](/3.x-beta/ko/guide/usage/handling-viewer-events.md)를 사용하세요. |
| `ref.current.scrollToIndex(...)` | [`useGestureViewerController().goToIndex(...)`, `goToNext()`, `goToPrevious()`](/3.x-beta/ko/guide/usage/programmatic-control.md)를 사용하세요. |
| `windowSize`, `maxToRenderPerBatch`, `removeClippedSubviews`, `estimatedItemSize` | 리스트 virtualization 튜닝 대신 [`windowSize`](/3.x-beta/ko/guide/usage/gesture-viewer-props.md#windowsize)를 사용하세요. 기본값은 이전/현재/다음만 마운트합니다. |
| `showsHorizontalScrollIndicator`, `contentContainerStyle`, list separators | 제거됨. 시각적 구성은 [`renderItem`](/3.x-beta/ko/guide/usage/custom-components.md) 내부에서 조합하거나, 페이지 사이 간격은 [`pageSpacing`](/3.x-beta/ko/guide/usage/gesture-viewer-props.md#pagespacing)을 사용하세요. |
## 참고
- [`renderItem`](/3.x-beta/ko/guide/usage/custom-components.md)은 이미지, 비디오, 커스텀 콘텐츠를 렌더링하는 커스터마이징 지점으로 유지됩니다.
- [`useGestureViewerState`](/3.x-beta/ko/guide/usage/gesture-viewer-state.md)는 계속 `currentIndex`와 `totalCount`를 제공합니다.
- [`useGestureViewerController`](/3.x-beta/ko/guide/usage/programmatic-control.md)는 계속 프로그래밍 방식 제어 surface로 사용합니다.
---
url: /3.x-beta/ko/guide/performance-optimization-tips.md
---
# 성능 최적화 팁
#### `renderItem` 함수는 `useCallback`으로 감싸서 불필요한 리렌더링을 방지하세요.
```tsx
import { useCallback, useState } from 'react'; // [!code highlight]
import { Image, Modal } from 'react-native';
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
const images = [...];
const [visible, setVisible] = useState(false);
// [!code highlight:3]
const renderImage = useCallback((imageUrl: string) => {
return ;
}, []);
return (
setVisible(false)}>
setVisible(false)}
/>
);
}
```
#### 대용량 이미지의 경우 [`expo-image`](https://docs.expo.dev/versions/latest/sdk/image/)나 [`FastImage`](https://github.com/DylanVann/react-native-fast-image) 사용을 권장합니다.
```tsx
import { Modal } from 'react-native';
import { GestureViewer } from 'react-native-gesture-image-viewer';
import { Image } from 'expo-image'; // [!code highlight]
function App() {
const images = [...];
const [visible, setVisible] = useState(false);
const renderImage = useCallback((imageUrl: string) => {
return ; // [!code highlight]
}, []);
return (
setVisible(false)}>
setVisible(false)}
/>
);
}
```
#### 인접 페이지를 더 많이 마운트해야 하는 경우가 아니라면 render window를 작게 유지하세요.
기본 `windowSize`는 `3`이며 이전, 현재, 다음 아이템을 마운트합니다. 더 큰 window는 인접 페이지를 더 빨리 보여줄 수 있지만 렌더링, 메모리, 이미지 디코딩 작업도 늘어납니다. 라이브러리는 `windowSize`에 하드 최대값을 두지 않으므로, 큰 값을 선택하는 것은 명시적인 성능 tradeoff입니다.
```tsx
import { useCallback, useState } from 'react';
import { 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)}
/>
);
}
```
#### 디바이스에서 테스트하세요. (시뮬레이터에서는 성능이 제한될 수 있습니다.)
---
url: /3.x-beta/ko/guide/usage/basic-usage.md
---
# 기본 사용법
## GestureViewer
```tsx
import { 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)}
/>
);
}
```
## [useGestureViewerController](/3.x-beta/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](/3.x-beta/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](/3.x-beta/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](/3.x-beta/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: /3.x-beta/ko/guide/usage/custom-components.md
---
# 커스텀 컴포넌트
`react-native-gesture-image-viewer`는 강력한 기능으로 완벽한 컴포넌트 커스터마이징이 가능합니다. 이미지뿐만 아니라 원하는 컴포넌트로 제스처를 지원하는 아이템을 만들 수 있습니다.
## 모달 컴포넌트
다음과 같이 원하는 `Modal`을 사용하여 뷰어를 만들 수 있습니다.
**Use Modal**
```tsx
import { 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 { 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)}
/>
);
}
```
## Render Window
3.x는 사용자가 넘기는 리스트 컴포넌트 대신 내부 render window를 사용합니다. 콘텐츠 커스터마이징은 `renderItem`을 사용하고, 기본 window가 부족할 때만 `windowSize`나 `pageSpacing`을 조정하세요.
```tsx
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 { GestureViewer } from 'react-native-gesture-image-viewer';
import { Image } from 'expo-image';
function App() {
const renderImage = useCallback((imageUrl: string) => {
return (
);
}, []);
return ;
}
```
### `renderItem` 활성 상태 (`isActive`)
비디오처럼 현재 표시 중인 콘텐츠에서만 재생이나 작업을 수행해야 한다면 세 번째 `renderItem` 인자의 `isActive`를 사용할 수 있습니다. 이 값은 현재 선택된 콘텐츠에서만 `true`입니다. 페이지 전환 중에는 기존 콘텐츠가 활성 상태를 유지하고, 이동이 완료되면 새로 선택된 콘텐츠가 활성화됩니다. 아이템 외부 UI에서 전역 `currentIndex`가 필요할 때는 [`useGestureViewerState`](/3.x-beta/ko/guide/usage/gesture-viewer-state.md)를 사용하세요.
```tsx
import type { GestureViewerRenderItemInfo } from 'react-native-gesture-image-viewer';
const renderMedia = useCallback(
// [!code highlight:1]
(item: MediaItem, _index: number, { isActive }: GestureViewerRenderItemInfo) => {
return (
);
},
[],
);
return ;
```
---
url: /3.x-beta/ko/guide/usage/gesture-features.md
---
# 제스처 기능
`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` |
### `horizontalSwipe`
사용자가 직접 수행하는 좌우 페이지 스와이프 제스처를 제어합니다. 비활성화해도 controller 이동과 autoplay는 계속 사용할 수 있습니다.
기본값:
- `enabled`: `true`
- `distanceThresholdRatio`: 뷰어 너비의 `0.25`
- `velocityThreshold`: 초당 `800` points
절대 가로 이동 거리가 `width * distanceThresholdRatio`보다 엄격히 클 때, 또는 절대 가로 속도가 `velocityThreshold`보다 엄격히 클 때 페이지가 전환됩니다. 거리와 속도는 엄격한 `>` OR 조건으로 동작합니다. `0`과 모든 유한한 0 이상의 값은 허용되며, `1`보다 큰 거리 비율도 허용됩니다. 음수와 유한하지 않은 값은 기본값으로 대체됩니다.
```tsx
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
return (
);
}
```
사용자/마우스 가로 제스처만 비활성화합니다.
```tsx
```
거리 중심 설정을 사용합니다.
```tsx
```
속도 중심 설정을 사용합니다.
```tsx
```
사용자 제스처를 비활성화해도 autoplay는 계속 동작합니다.
```tsx
```
### `enablePinchZoom` (기본값: `true`)
두 손가락 핀치 제스처를 제어합니다. `false`일 때 핀치 줌 제스처가 비활성화됩니다. 핀치 줌은 두 손가락 사이의 중심점을 기준으로 확대됩니다.
### `enableDoubleTapZoom` (기본값: `true`)
더블탭 줌 제스처를 제어합니다. `false`일 때 더블탭 줌 제스처가 비활성화됩니다. 더블탭 줌은 탭한 위치를 중심으로 확대됩니다.
### `enablePanWhenZoomed` (기본값: `true`)
줌이 활성화된 상태에서만 작동하며, 확대된 상태에서 아이템 위치를 이동할 수 있게 합니다. `false`일 때 줌 상태에서의 제스처 이동이 비활성화됩니다. 콘텐츠가 화면 밖으로 나가지 않도록 자동으로 경계를 제한합니다.
---
url: /3.x-beta/ko/guide/usage/gesture-viewer-props.md
---
# GestureViewer Props
### `renderItem`
`renderItem`은 `data`의 각 아이템을 렌더링하며, `renderItem(item, index, { isActive })` 형태로 세 번째 인자에서 활성 상태를 제공합니다. 비디오 재생처럼 활성 아이템에서만 필요한 작업을 제어할 때 사용할 수 있습니다. 동작 방식과 예시는 [`renderItem` 활성 상태 (`isActive`)](/3.x-beta/ko/guide/usage/custom-components.md#renderitem-active-state)를 참고하세요.
### `enableLoop` (기본값: `false`)
루프 모드를 활성화합니다. `true`일 때 마지막 아이템에서 다음으로 가면 첫 번째로, 첫 번째에서 이전으로 가면 마지막으로 돌아갑니다.
```tsx
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
return (
);
}
```
### `windowSize` (기본값: `3`)
현재 아이템을 포함해 내부 render-window slot을 몇 개 마운트할지 제어합니다. 값은 최소 `3` 이상의 홀수로 정규화됩니다. 예를 들어 `4`는 `5`가 됩니다.
하드 최대값은 없습니다. 큰 값은 더 많은 `renderItem` 콘텐츠를 의도적으로 마운트하므로, 메모리, 렌더링, 이미지 디코딩 비용을 감수하는 명시적인 선택으로 다루세요.
```tsx
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
return (
);
}
```
### `pageSpacing` (기본값: `0`)
페이지 사이의 가로 시각 간격을 설정합니다. 페이지 stride는 `width + pageSpacing`이며, zoom bounds는 계속 `width`와 `height`를 사용합니다.
```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]
/>
);
}
```
### `initialIndex` (기본값: `0`)
초기 인덱스 값을 설정할 수 있습니다.
### `maxZoomScale` (기본값: `2`)
최대 줌 배율을 제어할 수 있습니다.
## `GestureViewerProps` interface
[Go to source](https://github.com/saseungmin/react-native-gesture-image-viewer/blob/next/src/types.ts)
````tsx title="GestureViewerProps"
export type GestureViewerRenderItemInfo = {
/**
* 렌더링된 아이템이 현재 활성 상태인지 여부입니다.
* @remarks 페이지 전환 중에는 기존 아이템이 활성 상태를 유지합니다. 전환이 완료되어 다른 아이템으로 이동하면 해당 아이템이 활성화됩니다.
*/
readonly isActive: boolean;
};
export interface GestureViewerProps {
/**
* 여러 개의 `GestureViewer` 인스턴스를 효율적으로 관리하고 싶다면 `id` prop을 사용해 각각의 `GestureViewer`를 구분할 수 있습니다.
* @remarks `GestureViewer`는 컴포넌트가 언마운트되면 인스턴스를 메모리에서 자동으로 제거하므로 별도의 수동 메모리 관리가 필요하지 않습니다.
* @defaultValue 'default'
*/
id?: string;
/**
* `GestureViewer`에 표시할 데이터입니다.
*/
data: ItemT[];
/**
* 컴포넌트가 마운트될 때 `GestureViewer`에 처음 표시할 아이템의 인덱스입니다.
* @defaultValue 0
*/
initialIndex?: number;
/**
* `GestureViewer`가 닫힐 때 호출되는 콜백 함수입니다.
*/
onDismiss?: () => void;
/**
* dismiss 인터랙션이 시작될 때 호출되는 콜백 함수입니다.
* @remarks dismiss 제스처/애니메이션이 진행되는 동안 외부 UI(예: 헤더, 버튼)를 숨길 때 유용합니다.
*/
onDismissStart?: () => void;
/**
* 아이템을 렌더링할 때 호출되는 콜백 함수입니다.
* @param item - 렌더링할 아이템입니다.
* @param index - `data` 배열에서 아이템의 인덱스입니다.
* @param info - 마운트된 이 slot의 렌더링 상태입니다.
*/
renderItem: (item: ItemT, index: number, info: GestureViewerRenderItemInfo) => React.ReactElement;
/**
* 뷰어 콘텐츠에서 싱글 탭이 확정되었을 때 호출되는 콜백 함수입니다.
* @remarks
* - 이 콜백은 탭이 싱글 탭으로 확정된 뒤에만 실행되므로, 더블 탭 줌이 활성화되어 있으면 약간 지연될 수 있습니다.
* - 스와이프, 핀치, dismiss, 더블 탭 줌 제스처에서는 호출되지 않습니다.
* - 전체 화면 탭 처리가 필요하다면 `renderContainer`에서 pressable을 덮어씌우는 대신 이 콜백 사용을 권장합니다.
*/
onSingleTap?: (event: GestureViewerSingleTapEvent) => void;
/**
* 컨테이너를 렌더링할 때 호출되는 콜백 함수입니다.
* @remarks 뷰어 주변에 추가 UI(예: 닫기 버튼, 툴바)를 구성할 때 유용합니다.
* 전체 화면 탭 처리는 뷰어 콘텐츠 위에 pressable을 덮어씌우기보다 `onSingleTap` 사용을 권장합니다.
* 두 번째 인자는 `dismiss()`처럼 뷰어를 닫을 수 있는 제어 헬퍼를 제공합니다.
*
* @param children - 컨테이너 내부에 렌더링할 뷰어 콘텐츠입니다.
* @param helpers - 뷰어 제어 헬퍼입니다. 현재는 `dismiss()`를 포함합니다.
* @returns 전달된 `children`을 감싸서 렌더링하는 React 엘리먼트입니다.
*/
renderContainer?: (
children: React.ReactElement,
helpers: { dismiss: () => void },
) => React.ReactElement;
/**
* `GestureViewer`의 너비입니다.
* @remarks 이 prop을 지정하지 않으면 `GestureViewer`의 너비는 화면 너비와 동일해집니다.
* @defaultValue 화면 너비
*/
width?: number;
/**
* `GestureViewer`의 높이입니다.
* @remarks 이 prop을 지정하지 않으면 `GestureViewer`의 높이는 화면 높이와 동일해집니다.
* @defaultValue 화면 높이
*/
height?: number;
/**
* 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 사용자가 직접 수행하는 좌우 페이지 스와이프 제스처를 제어합니다. 비활성화해도 controller 이동과 autoplay는 계속 사용할 수 있습니다.
*/
horizontalSwipe?: {
/**
* `false`이면 사용자가 직접 수행하는 가로 페이지 스와이프 제스처가 비활성화됩니다.
* @defaultValue true
*/
enabled?: boolean;
/**
* 뷰어 너비에 대한 거리 임계값 비율입니다.
* @remarks 절대 가로 이동 거리가 `width * distanceThresholdRatio`보다 엄격히 클 때, 또는 속도가 `velocityThreshold`를 넘을 때 페이지가 전환됩니다. `0`과 `1`보다 큰 유한한 값은 허용되며, 음수와 유한하지 않은 값은 기본값으로 대체됩니다.
* @defaultValue 0.25
*/
distanceThresholdRatio?: number;
/**
* 초당 points 단위의 가로 속도 임계값입니다.
* @remarks 절대 가로 속도가 `velocityThreshold`보다 엄격히 클 때, 또는 거리가 `distanceThresholdRatio`를 넘을 때 페이지가 전환됩니다. `0`과 모든 유한한 0 이상의 값은 허용되며, 음수와 유한하지 않은 값은 기본값으로 대체됩니다.
* @defaultValue 800
*/
velocityThreshold?: number;
};
/**
* 줌이 활성화된 경우에만 동작하며, 확대된 상태에서 아이템 위치를 이동할 수 있게 합니다.
* @remarks `false`이면 줌 중 제스처 이동이 비활성화됩니다.
* @defaultValue true
*/
enablePanWhenZoomed?: boolean;
/**
* 두 손가락 핀치 제스처를 제어합니다.
* @remarks `false`이면 두 손가락 줌 제스처가 비활성화됩니다.
* @defaultValue true
*/
enablePinchZoom?: boolean;
/**
* 더블 탭 줌 제스처를 제어합니다.
* @remarks `false`이면 더블 탭 줌 제스처가 비활성화됩니다.
* @defaultValue true
*/
enableDoubleTapZoom?: boolean;
/**
* 무한 루프 탐색을 활성화합니다.
* @defaultValue false
*/
enableLoop?: boolean;
/**
* 현재 아이템을 포함해 마운트할 render-window 슬롯 수입니다.
* @remarks 값은 최소 3 이상의 홀수로 정규화됩니다. 예를 들어 `4`는 `5`가 됩니다.
* @defaultValue 3
*/
windowSize?: number;
/**
* 페이지 사이의 가로 시각 간격입니다.
* @remarks 페이지 stride는 `width + pageSpacing`이며, zoom bounds는 계속 `width`와 `height`를 사용합니다.
* @defaultValue 0
*/
pageSpacing?: number;
/**
* 최대 줌 배율입니다.
* @defaultValue 2
*/
maxZoomScale?: number;
/**
* 트리거 기반 애니메이션 설정입니다.
* @remarks 애니메이션 지속 시간, easing, 시스템 reduce-motion 동작을 커스터마이즈할 수 있습니다.
*
* @example
* ```tsx
* {
* console.log('애니메이션 완료');
* },
* }}
* />
* ```
*/
triggerAnimation?: TriggerAnimationConfig;
}
````
---
url: /3.x-beta/ko/guide/usage/gesture-viewer-state.md
---
# 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: /3.x-beta/ko/guide/usage/handling-viewer-events.md
---
# 뷰어 이벤트 처리하기
`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`](/3.x-beta/ko/guide/usage/gesture-viewer-props.md#onsingletap)을 사용할 수 있습니다.
:::
---
url: /3.x-beta/ko/guide/usage/multi-instance-management.md
---
# 다중 인스턴스 관리
여러 개의 `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: /3.x-beta/ko/guide/usage/programmatic-control.md
---
# 프로그래밍 방식 제어
`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, options?: { animated?: boolean }) => 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` |
페이지 전환 애니메이션 없이 현재 아이템을 즉시 바꿔야 한다면 `goToIndex(index, { animated: false })`를 사용하세요.
v3에서는 인접한 이동(`goToPrevious`, `goToNext`, 또는 이전/다음 아이템으로 이동하는 `goToIndex`)에만 페이지 전환 애니메이션을 사용합니다. 인접하지 않은 `goToIndex` 호출은 작은 render window만 마운트하는 구조이므로 즉시 rebase됩니다.
- `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: /3.x-beta/ko/guide/usage/style-customization.md
---
# 스타일 커스터마이징
`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: /3.x-beta/ko/guide/usage/trigger-based-animations.md
---
# 트리거 기반 모달 애니메이션
`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: /3.x-beta/ko/index.md
---
# React Native Gesture Image Viewer
부드럽고 유연한 이미지 뷰어
> 리스트 없는 제스처 페이징을 위한 v3 beta 문서
[빠른 시작](/3.x-beta/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, Expo Image, FastImage, 커스텀 콘텐츠와 매끄럽게 연동
- 🧠 **완벽한 TypeScript 지원**: 타입 추론과 안정성을 갖춘 뛰어난 개발 경험 제공
- 🌐 **크로스 플랫폼 지원**: iOS, Android, Web에서 동작하며 Expo Go 및 New Architecture 지원
- 🪄 **간편한 API**: 복잡한 설정 없이도 직관적이고 쉽게 구현 가능