---
url: /guide/getting-started/overview.md
---
# Overview

Have you ever struggled with implementing complex gesture handling and animations when building image galleries or content viewers in React Native?
Existing libraries often have limited customization options or performance issues. `react-native-gesture-image-viewer` is a high-performance **universal gesture viewer** library built on [**React Native Reanimated**](https://docs.swmansion.com/react-native-reanimated/) and [**Gesture Handler**](https://docs.swmansion.com/react-native-gesture-handler/docs/), providing complete customization and intuitive gesture support for not only images but also videos, custom components, and any other content.
## Key Features
- π€ **Complete Gesture Support** - Pinch zoom, double-tap zoom, swipe navigation, pan when zoomed-in, and vertical drag to dismiss
- ποΈ **High-Performance Animations** - Smooth and responsive animations at 60fps and beyond, powered by React Native Reanimated
- π¨ **Full Customization** - Total control over components, styles, and gesture behavior
- ποΈ **External Control API** - Trigger actions programmatically from buttons or other UI components
- π§© **Multi-Instance Management** - Manage multiple viewers independently using unique IDs
- 𧬠**Flexible Integration** - Use with 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), and more
- π§ **Full TypeScript Support** - Great developer experience with type inference and safety
- π **Cross-Platform Support** - Runs on iOS, Android, and Web with Expo Go and New Architecture compatibility
- πͺ **Easy-to-Use API** - Simple and intuitive API that requires minimal setup
---
url: /guide/getting-started/installation.md
---
# Installation
:::warning React Native Reanimated v3 Users
If you're using **React Native Reanimated v3**, **version 1.x** of this library is recommended for the best experience.
π **[Go to v1.x Documentation](/1.x/guide/getting-started/installation.md)**
Version 2.x only supports React Native Reanimated v4.
:::
:::info Important
`react-native-gesture-image-viewer` is a high-performance viewer library built on [`react-native-reanimated`](https://www.npmjs.com/package/react-native-reanimated) and [`react-native-gesture-handler`](https://www.npmjs.com/package/react-native-gesture-handler).\
Therefore, you **must install** React Native Reanimated and Gesture Handler before using this library. Please refer to the official documentation of these libraries for detailed setup guides.
:::
#### Minimum Requirements
| Library | Minimum Version |
| :----------------------------- | :-------------: |
| `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 Setup](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/)
### Install `react-native-reanimated` and `react-native-worklets` packages:
```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` was separated from `react-native-reanimated` for better modularity and must be installed separately.
```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
```
### Configure Babel
#### Expo
:::warning Expo Go Support
To use Reanimated 4 in Expo Go environment, you need to use [Expo SDK 54(beta)](https://expo.dev/go).
:::
Run prebuild to update the native code in the ios and android directories.
```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
```
And that's it! Reanimated 4 is now configured in your Expo project.\
Since Expo SDK 50, the Expo starter template includes the Reanimated babel plugin by default.
#### React Native CLI
When using React Native Community CLI, you also need to manually add the `react-native-worklets/plugin` plugin to your `babel.config.js`.
```js title="babel.config.js"
module.exports = {
presets: [
... // don't add it here :)
],
plugins: [
...
// for web
'@babel/plugin-proposal-export-namespace-from', // [!code highlight]
// react-native-worklets/plugin has to be listed last.
'react-native-worklets/plugin', // [!code highlight]
],
};
```
## [React Native Gesture Handler Setup](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` generally doesn't require additional setup, but please refer to the official documentation for your specific environment.
- For [using gestures in Android modals](https://docs.swmansion.com/react-native-gesture-handler/docs/fundamentals/installation#android), you would normally need to wrap modal content with `GestureHandlerRootView`. However, **this library already includes `GestureHandlerRootView` internally, so no additional wrapping is needed when using modals.**
## Install React Native Gesture Image Viewer
Youβre all set! π\
Start by installing `react-native-gesture-image-viewer`
```sh [npm]
npm install react-native-gesture-image-viewer
```
```sh [yarn]
yarn add react-native-gesture-image-viewer
```
```sh [pnpm]
pnpm add react-native-gesture-image-viewer
```
```sh [bun]
bun add react-native-gesture-image-viewer
```
```sh [deno]
deno add npm:react-native-gesture-image-viewer
```
---
url: /guide/getting-started/quick-start.md
---
# Quick Start

## Examples & Demo
- [π Example Project](https://github.com/saseungmin/react-native-gesture-image-viewer/tree/main/example) - Real implementation code with various use cases
- [π€ Expo Go](https://snack.expo.dev/@harang/react-native-gesture-image-viewer-v2) - Try it instantly on Expo Snack
## Installation
:::warning Important
`react-native-gesture-image-viewer` is a high-performance viewer library built on [`react-native-reanimated`](https://www.npmjs.com/package/react-native-reanimated) and [`react-native-gesture-handler`](https://www.npmjs.com/package/react-native-gesture-handler).\
Therefore, you **must install** React Native Reanimated and Gesture Handler before using this library. **If you haven't set it up yet, please refer to the [installation guide](/guide/getting-started/installation.md).**
:::
```sh [npm]
npm install react-native-gesture-image-viewer
```
```sh [yarn]
yarn add react-native-gesture-image-viewer
```
```sh [pnpm]
pnpm add react-native-gesture-image-viewer
```
```sh [bun]
bun add react-native-gesture-image-viewer
```
```sh [deno]
deno add npm:react-native-gesture-image-viewer
```
## Basic Usage
`react-native-gesture-image-viewer` is a library focused purely on gesture interactions for complete customization.
```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)}
/>
);
}
```
---
url: /guide/getting-started/ai.md
---
# AI
To help AI better understand this library's features, versioned documentation, and project conventions so it can provide more accurate help during development and troubleshooting, this project provides the following capabilities:
## llms.txt
[`llms.txt`](https://llmstxt.org/) helps LLMs discover and use project documentation. This site publishes the following files.
- [llms.txt](https://react-native-gesture-image-viewer.pages.dev/llms.txt): The structured index file for the current `2.x` docs.
```txt
https://react-native-gesture-image-viewer.pages.dev/llms.txt
```
- [llms-full.txt](https://react-native-gesture-image-viewer.pages.dev/llms-full.txt): The full-content file that concatenates the complete `2.x` documentation into a single file.
```txt
https://react-native-gesture-image-viewer.pages.dev/llms-full.txt
```
Choose the file that best fits your use case:
- `llms.txt` is smaller and consumes fewer tokens, so it works well when AI should fetch specific pages on demand.
- `llms-full.txt` contains the complete documentation for the current version, so AI does not need to follow individual links. This is useful for broader understanding or larger refactors, but it consumes more tokens.
## Markdown docs
Every documentation page also has a corresponding Markdown version that can be passed directly to AI.
Examples:
```txt
https://react-native-gesture-image-viewer.pages.dev/guide/getting-started/overview.md
https://react-native-gesture-image-viewer.pages.dev/guide/getting-started/quick-start.md
https://react-native-gesture-image-viewer.pages.dev/guide/usage/programmatic-control.md
```
Providing a single Markdown page is often the most efficient option when you want help with one specific feature.
---
url: /guide/migration-from-1.x.md
---
# Migrating from 1.x to 2.x
## [Migration from Reanimated 3.x to 4.x](https://docs.swmansion.com/react-native-reanimated/docs/guides/migration-from-3.x)
### Migration Steps
### Upgrade the `react-native-reanimated` package:
```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
```
### Install the `react-native-worklets` package:
```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
```
### Update Configuration
#### Expo
Run prebuild to update the native code in the `ios` and `android` directories.
```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
```
And that's it! Reanimated 4 is now configured in your Expo project.\
Since Expo SDK 50, the Expo starter template includes the Reanimated Babel plugin by default.
#### React Native CLI
When using the React Native Community CLI, you also need to manually add the `react-native-worklets/plugin` plugin to your `babel.config.js`.
```diff title="babel.config.js"
module.exports = {
presets: [
... // don't add it here :)
],
plugins: [
...
// for web
'@babel/plugin-proposal-export-namespace-from', // [!code highlight]
- 'react-native-reanimated/plugin',
+ 'react-native-worklets/plugin',
],
};
```
Remove `wrapWithReanimatedMetroConfig` from your `metro.config.js`.
```diff title="metro.config.js"
- const { wrapWithReanimatedMetroConfig } = require('react-native-reanimated/metro-config');
const config = {
// Your existing Metro configuration options
};
- module.exports = wrapWithReanimatedMetroConfig(config);
+ module.exports = config
```
## Remove `currentIndex` and `totalCount` from `useGestureViewerController`
:::info β Breaking Change
- `useGestureViewerController` no longer returns `currentIndex` and `totalCount`. Use `useGestureViewerState` instead.
- Rename `GestureViewerControllerState` type to `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();
```
## Remove `onIndexChange` prop
:::info β Breaking Change
`onIndexChange` prop has been removed. Use `useGestureViewerState` with `useEffect` instead.
:::
```tsx
// Before
console.log(index)} />;
// After
const { currentIndex } = useGestureViewerState();
useEffect(() => {
console.log(currentIndex);
}, [currentIndex]);
```
## Rename gesture props
Improve gesture prop naming for better developer experience.
:::info β Breaking Change
- `enableDismissGesture` β `dismiss.enabled`
- `dismissThreshold` β `dismiss.threshold`
- `resistance` β `dismiss.resistance`
- `animateBackdrop` β `dismiss.fadeBackdrop`
- `useSnap` β `enableSnapMode`
- `enableZoomPanGesture` β `enablePanWhenZoomed`
- `enableZoomGesture` β `enablePinchZoom`
- `enableSwipeGesture` β `enableHorizontalSwipe`
:::
```diff
```
---
url: /guide/performance-optimization-tips.md
---
# Performance Optimization Tips
#### Wrap the `renderItem` function with `useCallback` to prevent unnecessary re-renders.
```tsx
import { useCallback, useState } from 'react'; // [!code highlight]
import { ScrollView, 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)}
/>
);
}
```
#### For large images, we recommend using [`expo-image`](https://docs.expo.dev/versions/latest/sdk/image/) or [`FastImage`](https://github.com/DylanVann/react-native-fast-image).
```tsx
import { ScrollView, 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)}
/>
);
}
```
#### For handling many images, we recommend using [`FlashList`](https://shopify.github.io/flash-list/).
- [FlashList Performance Guide](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]
function App() {
const images = [...];
const [visible, setVisible] = useState(false);
const renderImage = useCallback((imageUrl: string) => {
return ;
}, []);
return (
setVisible(false)}>
setVisible(false)}
/>
);
}
```
#### Test on actual devices (performance may be limited in simulators).
---
url: /guide/usage/basic-usage.md
---
# Basic Usage
## GestureViewer
```tsx
import { ScrollView, 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](/guide/usage/programmatic-control.md)
You can programmatically control the `GestureViewer` using the `useGestureViewerController` hook.
```tsx
import { GestureViewer, useGestureViewerController } from 'react-native-gesture-image-viewer';
function App() {
const { goToIndex, goToPrevious, goToNext, zoomIn, zoomOut, resetZoom, rotate } =
useGestureViewerController();
return (
goToIndex(2)} />
);
}
```
## [useGestureViewerState](/guide/usage/gesture-viewer-state.md)
You can get the current state of the `GestureViewer` using the `useGestureViewerState` hook.
```tsx
import { GestureViewer, useGestureViewerState } from 'react-native-gesture-image-viewer';
function App() {
const { currentIndex, totalCount } = useGestureViewerState();
return (
{`${currentIndex + 1} / ${totalCount}`}
);
}
```
## [useGestureViewerEvent](/guide/usage/handling-viewer-events.md)
You can subscribe to specific events from `GestureViewer` using the `useGestureViewerEvent` hook. This allows you to respond to real-time gesture changes like zoom and rotation.
```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](/guide/usage/trigger-based-animations.md)
`GestureTrigger` supports smooth trigger-based animations that create seamless transitions from trigger elements (like thumbnails) to the full modal view. This feature enhances user experience by maintaining visual continuity between the trigger and modal content.
```tsx
import { GestureTrigger, GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
const [visible, setVisible] = useState(false);
return (
setVisible(true)}>
setVisible(false)} />
);
}
```
---
url: /guide/usage/custom-components.md
---
# Custom Components
`react-native-gesture-image-viewer` offers powerful complete component customization. You can create gesture-supported items with not only images but any component you want.
## Modal Components
You can create a viewer using any `Modal` of your choice as shown below:
**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)}
/>
);
}
```
## List Components
Support for any list component like `ScrollView`, `FlatList`, [`FlashList`](https://shopify.github.io/flash-list/) through the `ListComponent` prop.\
The `listProps` provides **type inference based on the selected list component**, ensuring accurate autocompletion and type safety in your IDE.
```tsx
import { FlashList } from '@shopify/flash-list';
function App() {
return (
);
}
```
## Content Components
You can inject various types of content components like [`expo-image`](https://docs.expo.dev/versions/latest/sdk/image/), [`FastImage`](https://github.com/DylanVann/react-native-fast-image), etc., through the `renderItem` prop to use gestures.
```tsx
import { GestureViewer } from 'react-native-gesture-image-viewer';
import { Image } from 'expo-image';
function App() {
const renderImage = useCallback((imageUrl: string) => {
return (
);
}, []);
return ;
}
```
---
url: /guide/usage/gesture-features.md
---
# Gesture Features
`react-native-gesture-image-viewer` supports various gestures essential for viewers. Please refer to the examples below for default gesture actions.
```tsx
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
return (
);
}
```
## Gesture Props
### `dismiss`
Dismiss gesture options. Controls which vertical swipe direction can trigger `onDismiss`, making it useful for modal-style close gestures with backward-compatible downward dismiss by default.
| Property | Description | Type | Default |
| :------------- | :-------------------------------------------------------------------------------------------------------- | :------------------- | :-----: |
| `enabled` | When `false`, dismiss gesture is disabled. | `boolean` | `true` |
| `direction` | Controls which vertical swipe direction can dismiss. | `down`, `up`, `both` | `down` |
| `threshold` | `threshold` controls when `onDismiss` is called by applying a threshold value during vertical gestures. | `number` | `80` |
| `resistance` | `resistance` controls the range of vertical movement by applying resistance during dismiss gestures. | `number` | `2` |
| `fadeBackdrop` | By default, the background `opacity` gradually decreases as you drag in the configured dismiss direction. | `boolean` | `true` |
### `enableHorizontalSwipe` (default: `true`)
Controls left/right swipe gestures. When `false`, horizontal gestures are disabled.
### `enablePinchZoom` (default: `true`)
Controls two-finger pinch gestures with focal point zooming. When `false`, pinch zoom is disabled. Zoom centers on the point between your two fingers for intuitive scaling.
### `enableDoubleTapZoom` (default: `true`)
Controls double-tap zoom gestures with precision targeting. When `false`, double-tap zoom is disabled. Zoom centers exactly on the tapped location.
### `enablePanWhenZoomed` (default: `true`)
Enables panning when zoomed in with automatic boundary detection. When `false`, movement is disabled during zoom. Prevents content from moving outside visible screen area.
---
url: /guide/usage/gesture-viewer-props.md
---
# GestureViewer Props
### `enableLoop` (default: `false`)
Enables loop mode. When `true`, navigating next from the last item goes to the first item, and navigating previous from the first item goes to the last item.
```tsx
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
return (
);
}
```
### `enableSnapMode` (default: `false`)
Enables snap scrolling mode.
- **`false` (default)**: Paging mode (`pagingEnabled: true`)
- Scrolls by full screen size increments
- **`true`**: Snap mode (`snapToInterval` auto-calculated)
- `snapToInterval` is automatically calculated based on `width` and `itemSpacing` values
- Use this option when you need item spacing
```tsx
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
return (
);
}
```
### `itemSpacing` (default: `0`)
Sets the spacing between items in pixels. Only applied when `enableSnapMode` is `true`.
```tsx
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
return (
);
}
```
### `autoPlay` (default: `false`)
Enables auto play mode. When `true`, the viewer will automatically play the next item after the specified interval.
- When `enableLoop` is enabled, the viewer will loop back to the first item after the last item.
- When `enableLoop` is disabled, the viewer will stop at the last item.
- When there is only one item, auto-play is disabled.
- When zoom or rotate gestures are detected, the auto-play will be paused.
```tsx
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
return (
);
}
```
### `autoPlayInterval` (default: `3000`)
Sets the interval between auto play in milliseconds.\
Must be a positive integer. Values below 250ms are clamped to 250ms at runtime.
```tsx
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
return (
);
}
```
### `onSingleTap`
Runs when the viewer confirms a single tap. This is useful for toggling viewer chrome such as headers, footers, captions, and action buttons without overlaying another pressable on top of the viewer.
- May resolve slightly later when double-tap zoom is enabled because the viewer waits to confirm it is not a double tap
- Does not fire for swipe, pinch, dismiss, or double-tap zoom gestures
```tsx
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
const [showControls, setShowControls] = useState(true);
return (
setShowControls((prev) => !prev)} // [!code highlight]
/>
);
}
```
### `initialIndex` (default: `0`)
Sets the initial index value.
### `maxZoomScale` (default: `2`)
Controls the maximum zoom scale multiplier.
## `GestureViewerProps` interface
[Go to source](https://github.com/saseungmin/react-native-gesture-image-viewer/blob/a7b58a5fc9aa396e5b72ec1e6fe6ae27d94ed044/src/types.ts#L88)
````tsx title="GestureViewerProps"
export interface GestureViewerProps {
/**
* When you want to efficiently manage multiple `GestureViewer` instances, you can use the `id` prop to use multiple `GestureViewer` components.
* @remarks `GestureViewer` automatically removes instances from memory when components are unmounted, so no manual memory management is required.
* @defaultValue 'default'
*/
id?: string;
/**
* The data to display in the `GestureViewer`.
*/
data: ItemT[];
/**
* The index of the item to display in the `GestureViewer` when the component is mounted.
* @defaultValue 0
*/
initialIndex?: number;
/**
* A callback function that is called when the `GestureViewer` is dismissed.
*/
onDismiss?: () => void;
/**
* A callback function that is called when the dismiss interaction starts.
* @remarks Useful to hide external UI (e.g., headers, buttons) while the dismiss gesture/animation is in progress.
*/
onDismissStart?: () => void;
/**
* A callback function that is called to render the item.
*/
renderItem: (item: ItemT, index: number) => React.ReactElement;
/**
* A callback function that is called when a single tap is confirmed on the viewer content.
* @remarks
* - The callback runs only after the tap is resolved as a single tap, so it may be slightly delayed when double-tap zoom is enabled.
* - It is not called for swipe, pinch, dismiss, or double-tap zoom gestures.
* - Prefer this callback over overlaying a pressable in `renderContainer` for fullscreen tap handling.
*/
onSingleTap?: (event: GestureViewerSingleTapEvent) => void;
/**
* A callback function that is called to render the container.
* @remarks Useful for composing additional UI (e.g., close button, toolbars) around the viewer.
* Prefer `onSingleTap` for fullscreen tap handling instead of overlaying a pressable over the viewer content.
* The second argument provides control helpers such as `dismiss()` to close the viewer.
*
* @param children - The viewer content to be rendered inside your container.
* @param helpers - Control helpers for the viewer. Currently includes `dismiss()`.
* @returns A React element that wraps and renders the provided `children`.
*/
renderContainer?: (
children: React.ReactElement,
helpers: { dismiss: () => void },
) => React.ReactElement;
/**
* Support for any list component like `ScrollView`, `FlatList`, `FlashList` through the `ListComponent` prop.
*/
ListComponent: LC;
/**
* The width of the `GestureViewer`.
* @remarks If you don't set this prop, the width of the `GestureViewer` will be the same as the width of the screen.
* @defaultValue screen width
*/
width?: number;
/**
* The height of the `GestureViewer`.
* @remarks If you don't set this prop, the height of the `GestureViewer` will be the same as the height of the screen.
* @defaultValue screen height
*/
height?: number;
/**
* The props to pass to the list component.
* @remarks The `listProps` provides **type inference based on the selected list component**, ensuring accurate autocompletion and type safety in your IDE.
*/
listProps?: Partial>;
/**
* The style of the backdrop.
*/
backdropStyle?: StyleProp;
/**
* The style of the container.
*/
containerStyle?: StyleProp;
/**
* Auto play mode.
* @remarks
* - When `true`, the viewer will automatically play the next item after the specified interval.
* - When `enableLoop` is enabled, the viewer will loop back to the first item after the last item.
* - When `enableLoop` is disabled, the viewer will stop at the last item.
* - When there is only one item, auto-play is disabled.
* - When zoom or rotate gestures are detected, the auto-play will be paused.
* @defaultValue false
*/
autoPlay?: boolean;
/**
* Auto play interval.
* @remarks
* - When `autoPlay` is enabled, the viewer advances to the next item after the specified interval (ms).
* - Must be a positive integer. Values below 250ms are clamped to 250ms at runtime.
* @defaultValue 3000
*/
autoPlayInterval?: number;
/**
* Dismiss gesture options.
* @remarks Useful for closing modals with configurable vertical swipe gestures.
*/
dismiss?: {
/**
* When `false`, dismiss gesture is disabled.
* @defaultValue true
*/
enabled?: boolean;
/**
* Controls which vertical swipe direction can trigger `onDismiss`.
* @remarks Use `down` for backward-compatible behavior, `up` for upward-only dismiss, or `both` for either direction.
* @defaultValue 'down'
*/
direction?: GestureViewerDismissDirection;
/**
* `threshold` controls when `onDismiss` is called by applying a threshold value during vertical gestures.
* @defaultValue 80
*/
threshold?: number;
/**
* `resistance` controls the range of vertical movement by applying resistance during dismiss gestures.
* @defaultValue 2
*/
resistance?: number;
/**
* By default, the background `opacity` gradually decreases as you drag in the configured dismiss direction.
* @remarks When `false`, this animation is disabled.
* @defaultValue true
*/
fadeBackdrop?: boolean;
};
/**
* Controls left/right swipe gestures.
* @remarks When `false`, horizontal gestures are disabled.
* @defaultValue true
*/
enableHorizontalSwipe?: boolean;
/**
* Only works when zoom is active, allows moving item position when zoomed.
* @remarks When `false`, gesture movement is disabled during zoom.
* @defaultValue true
*/
enablePanWhenZoomed?: boolean;
/**
* Controls two-finger pinch gestures.
* @remarks When `false`, two-finger zoom gestures are disabled.
* @defaultValue true
*/
enablePinchZoom?: boolean;
/**
* Controls double-tap zoom gestures.
* @remarks When `false`, double-tap zoom gestures are disabled.
* @defaultValue true
*/
enableDoubleTapZoom?: boolean;
/**
* Enables infinite loop navigation.
* @defaultValue false
*/
enableLoop?: boolean;
/**
* Enables snap scrolling mode.
*
* @remarks
* **`false` (default)**: Paging mode (`pagingEnabled: true`)
* - Scrolls by full screen size increments
*
* **`true`**: Snap mode (`snapToInterval` auto-calculated)
* - `snapToInterval` is automatically calculated based on `width` and `itemSpacing` values
* - Use this option when you need item spacing
* @defaultValue false
*
*/
enableSnapMode?: boolean;
/**
* The spacing between items in pixels.
* @remarks Only applied when `enableSnapMode` is `true`.
* @defaultValue 0
*/
itemSpacing?: number;
/**
* The maximum zoom scale.
* @defaultValue 2
*/
maxZoomScale?: number;
/**
* Trigger-based animation settings
* @remarks You can customize animation duration, easing, and system reduce-motion behavior.
*
* @example
* ```tsx
* {
* console.log('Animation complete');
* },
* }}
* />
* ```
*/
triggerAnimation?: TriggerAnimationConfig;
}
````
---
url: /guide/usage/gesture-viewer-state.md
---
# GestureViewer State
`GestureViewer` provides a state management system to track the current state of the viewer. You can use the `useGestureViewerState` hook to access the state of the viewer.
## useGestureViewerState
```tsx
import { GestureViewer, useGestureViewerState } from 'react-native-gesture-image-viewer';
function App() {
const { currentIndex, totalCount } = useGestureViewerState();
return (
{`${currentIndex + 1} / ${totalCount}`}
);
}
```
### Parameters
- `id?: string`
- The ID of the `GestureViewer` instance.
- Default: `"default"`
### API Reference
| Property | Description | Type | Default Value |
| :------------- | :----------------------------------------- | :------- | :-----------: |
| `currentIndex` | The index of the currently displayed item. | `number` | `0` |
| `totalCount` | The total number of items. | `number` | `0` |
---
url: /guide/usage/handling-viewer-events.md
---
# Handling Viewer Events
`GestureViewer` provides a way to subscribe to specific events from the viewer using the `useGestureViewerEvent` hook. This allows you to respond to viewer interactions such as zoom, rotation, and taps.
## useGestureViewerEvent
```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 ;
}
```
### Listen to events on a specific instance
You can listen to events on a specific instance by passing the instance ID as the first argument to the `useGestureViewerEvent` hook.
```ts
// Listen to events on a specific instance
useGestureViewerEvent('gallery', 'zoomChange', (data) => {
console.log(`Gallery zoom: ${data.scale}x`);
});
```
:::note
The default `id` value is `default`.
:::
### API Reference
```ts
// Listen to events on the default instance
function useGestureViewerEvent(
eventType: T,
callback: GestureViewerEventCallback,
): void;
// Listen to events on a specific instance
function useGestureViewerEvent(
id: string,
eventType: T,
callback: GestureViewerEventCallback,
): void;
```
| Event Type | Description | Callback Data |
| :--------------- | :--------------------------------------------------------------------------------------- | :-------------------------------------------------------- |
| `zoomChange` | Fired when the zoom scale changes during pinch gestures | `{ scale: number, previousScale: number }` |
| `rotationChange` | Fired when the rotation angle changes during rotation gestures | `{ rotation: number, previousRotation: number }` |
| `tap` | Fired when a tap is confirmed by the viewer. Currently emits confirmed single taps only. | `{ kind: 'single', x: number, y: number, index: number }` |
:::tip
If you want to handle the `tap` event directly from a `GestureViewer` prop, you can use [`onSingleTap`](/guide/usage/gesture-viewer-props.md#onsingletap).
:::
---
url: /guide/usage/multi-instance-management.md
---
# Multi-Instance Management
When you want to efficiently manage multiple `GestureViewer` instances, you can use the `id` prop to use multiple `GestureViewer` components.
`GestureViewer` automatically removes instances from memory when components are unmounted, so no manual memory management is required.
:::note
The default `id` value is `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: /guide/usage/programmatic-control.md
---
# Programmatic Control
You can programmatically control the `GestureViewer` using the `useGestureViewerController` hook.
## 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)} />
);
}
```
### Parameters
- `id?: string`
- The ID of the `GestureViewer` instance.
- Default: `"default"`
### API Reference
| Property | Description | Type | Default Parameter |
| :------------- | :------------------------------------ | :----------------------------------------------------- | :---------------: |
| `goToIndex` | Navigate to a specific index. | `(index: number) => void` | - |
| `goToPrevious` | Navigate to the previous item. | `() => void` | - |
| `goToNext` | Navigate to the next item. | `() => void` | - |
| `zoomIn` | Zoom in by the specified multiplier. | `(multiplier?: number) => void` | `0.25` |
| `zoomOut` | Zoom out by the specified multiplier. | `(multiplier?: number) => void` | `0.25` |
| `resetZoom` | Reset zoom to the specified scale. | `(scale?: number) => void` | `1` |
| `rotate` | Rotate by the specified angle. | `(angle?: RotationAngle, clockwise?: boolean) => void` | `90, true` |
- `zoomIn(multiplier?)`
- **multiplier**: The multiplier for zooming in (range: `0.01 ~ 1`)
- Example: `zoomIn(0.5)` β Zoom in by an additional 50% of the current scale
- `zoomOut(multiplier?)`
- **multiplier**: The multiplier for zooming out (range: `0.01 ~ 1`)
- Example: `zoomOut(0.3)` β Zoom out by dividing the current scale by 1.3
- `resetZoom(scale?)`
- **scale**: The scale value to reset to
- Example: `resetZoom(1.5)` β Reset to 1.5x scale
- `rotate(angle?, clockwise?)`
- **angle**: The angle to rotate (`0`, `90`, `180`, `270`, `360`)
- **clockwise**: The direction to rotate (true: clockwise, false: counter-clockwise)
- Example: `rotate(90)` β Rotate 90 degrees clockwise
- Example: `rotate(90, false)` β Rotate 90 degrees counter-clockwise
- Example: `rotate(0)` β Reset rotation
---
url: /guide/usage/style-customization.md
---
# Style Customization
You can customize the styling of `GestureViewer`.
```tsx
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
return (
{children}}
/>
);
}
```
| Property | Description | Default Value |
| :----------------------------------: | :-------------------------------------------------------------------- | :------------------------------------------------: |
| `width` | The width of content items. Default is window width. | `Dimensions width` |
| `height` | The height of content items. Default is window height. | `Dimensions height` |
| `containerStyle` | Allows custom styling of the container that wraps the list component. | `flex: 1` |
| `backdropStyle` | Allows customization of the viewer's background style. | `backgroundColor: black; StyleSheet.absoluteFill;` |
| `renderContainer(children, helpers)` | Allows custom wrapper component around ``. | |
:::tip
Use `onSingleTap` for fullscreen tap handling. `renderContainer` is best for composing surrounding UI such as headers, close buttons, and toolbars.
:::
---
url: /guide/usage/trigger-based-animations.md
---
# Trigger-Based Modal Animations
`GestureTrigger` supports smooth trigger-based animations that create seamless transitions from trigger elements (like thumbnails) to the full modal view. This feature enhances user experience by maintaining visual continuity between the trigger and modal content.
## GestureTrigger Usage
The `GestureTrigger` component wraps pressable elements and registers their position for smooth modal transitions.
```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 (
{/* Gallery Grid */}
{images.map((uri, index) => (
openModal(index)}>
))}
{/* Modal */}
setVisible(false)}
triggerAnimation={{
duration: 300,
easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
onAnimationComplete: () => console.log('Animation finished!'),
}}
/>
);
}
```
### 1. Using onPress on GestureTrigger (Recommended)
```tsx
openModal(index)}>
```
### 2. Using onPress on Child Element
```tsx
openModal(index)}>
```
:::note important
- Both methods are functionally equivalent
- The `GestureTrigger` will automatically inject the press handler into the child component
- The child component must be pressable (support onPress prop)
- If both `GestureTrigger` and child have onPress, both will be called (child's handler first)
**Supported Child Components**
You can use any pressable component as a child, such as:
- `Pressable`
- `TouchableOpacity`
- `TouchableHighlight`
- `Button`
- Any custom component that accepts `onPress` prop
**Example with Different Components**
```tsx
// Using TouchableOpacity
View Image
;
// Using custom component
function CustomButton({ onPress, children }) {
return {children};
}
Custom Button
;
```
:::
## Animation Configuration
You can customize the trigger animation behavior using the `triggerAnimation` prop:
```tsx
import { ReduceMotion, Easing } from 'react-native-reanimated';
function App() {
return (
{
// Callback when animation finishes
console.log('Modal animation completed');
},
}}
/>
);
}
```
## Multiple Trigger Instances
You can have multiple trigger instances by using different IDs:
```tsx
// Photo gallery triggers
openPhotoModal(index)}>
// Video gallery triggers
openVideoModal(index)}>
```
:::tip
Make sure the `id` prop matches between `GestureTrigger` and `GestureViewer` components for the animation to work properly. (default value: `default`)
If you want the dismiss animation to return to the thumbnail for the **currently visible item**, also pass `index={index}` to each `GestureTrigger`.
:::
### Dismissing to the Current Item
In galleries where users can swipe to a different item after opening the modal, passing `index` lets the dismiss animation target the trigger for the **current viewer index** instead of always returning to the opening trigger.
```tsx
{
images.map((uri, index) => (
openModal(index)}>
));
}
```
If `index` is omitted, the dismiss animation falls back to the trigger that originally opened the viewer.
:::note Fallback behavior
If the trigger for the current index cannot be found (for example, because the thumbnail was unmounted by list virtualization), dismissal falls back in this order:
1. the trigger used to open the current modal session
2. a normal dismiss without trigger targeting
:::
## Handling Dismissal Animations
### onDismissStart Callback
The `onDismissStart` callback is triggered immediately when the dismiss animation begins, which is useful for hiding UI elements that should disappear before the animation completes.
```tsx
function App() {
const [visible, setVisible] = useState(false);
const [showExternalUI, setShowExternalUI] = useState(false); // [!code highlight]
// [!code highlight:3]
const handleDismissStart = () => {
setShowExternalUI(false);
};
return (
// [!code highlight:5]
{showExternalUI && (
{`${currentIndex + 1} / ${totalCount}`}
)}
);
}
```
### Dismissing from Custom Components
You can dismiss the viewer programmatically using the `dismiss` helper from `renderContainer`:
```tsx
( // [!code highlight]
{children}
Close
)}
/>
```
:::note Why Use renderContainer's dismiss?
When using trigger-based animations, it's important to use the `dismiss` method provided by `renderContainer` instead of directly controlling the visibility with `setVisible(false)`. Here's why:
```tsx
// β Avoid: This will close immediately without trigger animation
setVisible(false)} title="Close" />
// β
Preferred: This will use the trigger animation to close
```
**How It Works:**
1. With Trigger Animation:
- When `dismiss` is called, the viewer will animate back to the original trigger position
- `onDismissStart` is called at the start of the animation
- `onDismiss` is called after the animation completes
2. Without Trigger Animation:
- If no trigger animation is configured, `dismiss` will still work as a simple close
:::
### Complete Example with Dismiss Handling
```tsx
function ImageViewer() {
const [visible, setVisible] = useState(false);
const [showUI, setShowUI] = useState(true);
return (
setShowUI(false)}
onDismiss={() => setVisible(false)}
renderContainer={(children, { dismiss }) => (
{children}
{showUI && (
)}
)}
/>
);
}
```
### Dismiss Behavior with Trigger Animation
When using trigger-based animations, the dismiss animation will animate back to the original trigger position. The `onDismissStart` callback is called at the start of this animation, allowing you to hide any UI elements that should not be visible during the dismiss animation.
```tsx
{
console.log('Dismiss animation started');
setShowUI(false);
}}
onDismiss={() => {
console.log('Dismiss animation complete');
setVisible(false);
}}
/>
```
This pattern ensures a smooth user experience by:
1. Immediately hiding UI elements when dismiss starts
2. Allowing the dismiss animation to complete naturally
3. Cleaning up any resources only after the animation is fully complete
### Best Practices
1. Always use the `dismiss` method from `renderContainer` when you want to close the viewer with animations
2. Use `onDismissStart` to hide UI elements that shouldn't be visible during the dismiss animation
3. Use `onDismiss` for cleanup operations that should happen after the animation completes
#### Common Pitfalls
```tsx
// β Avoid: This will bypass the trigger animation
setVisible(false)} title="Close" />
// β Avoid: This will cause the animation to break
const handleClose = () => {
setShowUI(false);
setVisible(false); // Closes too early
};
// β
Correct: Let the animation complete naturally
setShowUI(false)}
onDismiss={() => setVisible(false)}
renderContainer={(children, { dismiss }) => (
{children}
{showUI && (
)}
)}
/>
```
This pattern ensures that your trigger-based animations work consistently and provides the best user experience.
## API Reference
### GestureTrigger Props
```ts
interface GestureTriggerProps {
id?: string; // Identifier to associate with GestureViewer (default: "default")
index?: number; // Item index used for current-item dismiss targeting
children: ReactElement; // Single pressable child element
onPress?: (...args: unknown[]) => void; // Additional onPress handler
}
```
### TriggerAnimation Config
```ts
interface TriggerAnimationConfig {
duration?: number; // Animation duration in milliseconds
easing?: EasingFunction; // Custom easing function
reduceMotion?: boolean; // Respect system reduce motion preference
onAnimationComplete?: () => void; // Callback fired when animation completes
}
```
| Property | Type | Default | Description |
| :-------------------- | :--------------- | :------------------------------------ | :----------------------------------------------- |
| `duration` | `number` | `300` | Animation duration in milliseconds |
| `easing` | `EasingFunction` | `Easing.bezier(0.25, 0.1, 0.25, 1.0)` | Easing function for the animation |
| `reduceMotion` | `ReduceMotion` | `undefined` | Whether to respect system reduce motion settings |
| `onAnimationComplete` | `() => void` | `undefined` | Callback fired when the animation completes |
:::note
The trigger animation works by measuring the trigger element's position when pressed and animating the modal from that position to full screen.
:::
---
url: /index.md
---
# React Native Gesture Image Viewer
Smooth and flexible viewer
> Reanimated-powered image gestures with full control
[Quick Start](/guide/getting-started/installation.html) | [GitHub](https://github.com/saseungmin/react-native-gesture-image-viewer)
## Features
- π€ **Complete Gesture Support**: Pinch zoom, double-tap zoom, swipe navigation, pan when zoomed-in, and vertical drag to dismiss
- ποΈ **High-Performance Animations**: Smooth and responsive animations at 60fps and beyond, powered by React Native Reanimated
- π¨ **Full Customization**: Total control over components, styles, and gesture behavior
- ποΈ **External Control API**: Trigger actions programmatically from buttons or other UI components
- π§© **Multi-Instance Management**: Manage multiple viewers independently using unique IDs
- 𧬠**Flexible Integration**: Works seamlessly with Modal, FlatList, FlashList, Expo Image, FastImage, and more
- π§ **Full TypeScript Support**: Great developer experience with type inference and safety
- π **Cross-Platform Support**: Runs on iOS, Android, and Web with Expo Go and New Architecture compatibility
- πͺ **Easy-to-Use API**: Simple and intuitive API that requires minimal setup