English | ํ๊ตญ์ด
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 and Gesture Handler, providing complete customization and intuitive gesture support for not only images but also videos, custom components, and any other content.
- โ Complete Gesture Support - Pinch zoom, double-tap zoom, swipe navigation, vertical drag dismiss
- โ High-Performance Animations - Smooth 60fps animations powered by React Native Reanimated
- โ Full Customization - Complete control over components, styles, and behaviors
- โ External Control API - Programmatic control from buttons or other components
- โ Multi-Instance Management - ID-based independent management of multiple viewers
- โ Flexible Integration - Use with Modal, React Native Modal, ScrollView, FlatList, FlashList, Expo Image, FastImage, and more
- โ Full TypeScript Support - Enhanced developer experience with smart type inference
- โ Cross-Platform - iOS, Android, and Web support
- โ Easy-to-Use API - Intuitive and simple implementation without complex setup
- โ Various Environment Support - Expo Go and New Architecture support
- ๐ Example Project - Real implementation code with various use cases
- ๐ค Expo Go - Try it instantly on Snack Expo
[!IMPORTANT]
react-native-gesture-image-viewer
is a high-performance viewer library built onreact-native-reanimated
andreact-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.
npm install react-native-reanimated
npm install react-native-gesture-handler
Library | Minimum Version |
---|---|
react |
>=18.0.0 |
react-native |
>=0.75.0 |
react-native-gesture-handler |
>=2.24.0 |
react-native-reanimated |
>=3.0.0 |
Add the plugin to your babel.config.js
:
// babel.config.js
module.exports = {
presets: [
... // don't add it here :)
],
plugins: [
...
// for web
'@babel/plugin-proposal-export-namespace-from',
// react-native-reanimated/plugin has to be listed last.
'react-native-reanimated/plugin',
],
};
Wrap your Metro config with wrapWithReanimatedMetroConfig
in metro.config.js
:
const {
wrapWithReanimatedMetroConfig,
} = require('react-native-reanimated/metro-config');
const config = {
// Your existing Metro configuration options
};
module.exports = wrapWithReanimatedMetroConfig(config);
-
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, you would normally need to wrap modal content with
GestureHandlerRootView
. However, this library already includesGestureHandlerRootView
internally, so no additional wrapping is needed when using modals.
# npm
npm install react-native-gesture-image-viewer
# pnpm
pnpm add react-native-gesture-image-viewer
# yarn
yarn add react-native-gesture-image-viewer
# bun
bun add react-native-gesture-image-viewer
react-native-gesture-image-viewer
is a library focused purely on gesture interactions for complete customization.
You can create a viewer using any Modal
of your choice as shown below:
import { FlatList, Image, Modal } from 'react-native';
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
const images = [...];
const [visible, setVisible] = useState(false);
// Wrap with useCallback for performance optimization
const renderImage = useCallback((imageUrl: string) => {
return <Image source={{ uri: imageUrl }} style={{ width: '100%', height: '100%' }} resizeMode="contain" />;
}, []);
return (
<Modal visible={visible} onRequestClose={() => setVisible(false)}>
<GestureViewer
data={images}
renderItem={renderImage}
ListComponent={FlatList}
onDismiss={() => setVisible(false)}
/>
</Modal>
);
}
react-native-gesture-image-viewer
supports various gestures essential for viewers. All gestures are enabled by default.
function App() {
return (
<GestureViewer
data={images}
renderItem={renderImage}
enableLoop={false}
enableDismissGesture
enableSwipeGesture
enableZoomGesture
enableDoubleTapGesture
enableZoomPanGesture
/>
)
}
Property | Description | Default |
---|---|---|
enableLoop |
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. |
false |
enableDismissGesture |
Calls onDismiss function when swiping down. Useful for closing modals with downward swipe gestures. |
true |
enableSwipeGesture |
Controls left/right swipe gestures. When false , horizontal gestures are disabled. |
true |
enableZoomGesture |
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. |
true |
enableDoubleTapGesture |
Controls double-tap zoom gestures with precision targeting. When false , double-tap zoom is disabled. Zoom centers exactly on the tapped location. |
true |
enableZoomPanGesture |
Enables panning when zoomed in with automatic boundary detection. When false , movement is disabled during zoom. Prevents content from moving outside visible screen area. |
true |
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.
Support for any list component like ScrollView
, FlatList
, FlashList
through the ListComponent
prop.
The listProps
provides type inference based on the selected list component, ensuring accurate autocompletion and type safety in your IDE.
import { FlashList } from '@shopify/flash-list';
function App() {
return (
<GestureViewer
data={images}
ListComponent={FlashList}
listProps={{
// โ
FlashList props autocompletion
}}
/>
);
}
You can inject various types of content components like expo-image
, FastImage
, etc., through the renderItem
prop to use gestures.
import { GestureViewer } from 'react-native-gesture-image-viewer';
import { Image } from 'expo-image';
function App() {
const renderImage = useCallback((imageUrl: string) => {
return <Image source={{ uri: imageUrl }} style={{ width: '100%', height: '100%' }} contentFit="contain" />;
}, []);
return (
<GestureViewer
data={images}
renderItem={renderImage}
/>
);
}
You can programmatically control the GestureViewer
using the useGestureViewerController
hook.
import { GestureViewer, useGestureViewerController } from 'react-native-gesture-image-viewer';
function App() {
const {
goToIndex,
goToPrevious,
goToNext,
currentIndex,
totalCount,
zoomIn,
zoomOut,
resetZoom,
rotate
} = useGestureViewerController();
return (
<View>
<GestureViewer
data={images}
renderItem={renderImage}
/>
{/* Zoom Controls */}
<View>
<Feather.Button name="zoom-in" onPress={() => zoomIn(0.25)} />
<Feather.Button name="zoom-out" onPress={() => zoomOut(0.25)} />
<Feather.Button
name="refresh-cw"
onPress={() => {
rotate(0);
resetZoom();
}}
/>
<Feather.Button name="rotate-cw" onPress={() => rotate(90)} />
<Feather.Button name="rotate-ccw" onPress={() => rotate(90, false)} />
</View>
{/* Navigation Controls */}
<View>
<Feather.Button name="chevron-left" onPress={goToPrevious} />
<Button title="Jump to index 2" onPress={() => goToIndex(2)} />
<Feather.Button name="chevron-right" onPress={goToNext} />
<Text>{`${currentIndex + 1} / ${totalCount}`}</Text>
</View>
</View>
);
}
Property | Description | Type | Default |
---|---|---|---|
goToIndex |
Navigate to a specific index. | (index: number) => void |
- |
goToPrevious |
Navigate to the previous item. | () => void |
- |
goToNext |
Navigate to the next item. | () => void |
- |
currentIndex |
The index of the currently displayed item. | number |
0 |
totalCount |
The total number of items. | number |
0 |
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?: number, 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
-
multiplier: The multiplier for zooming in (range:
-
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
-
multiplier: The multiplier for zooming out (range:
-
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
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.
import { GestureViewer, useGestureViewerEvent } from 'react-native-gesture-image-viewer';
function App() {
// Listen to zoom changes on the default instance (ID: 'default')
useGestureViewerEvent('zoomChange', (data) => {
console.log(`Zoom changed from ${data.previousScale} to ${data.scale}`);
});
// Listen to rotation changes on the default instance (ID: 'default')
useGestureViewerEvent('rotationChange', (data) => {
console.log(`Rotation changed from ${data.previousRotation}ยฐ to ${data.rotation}ยฐ`);
});
// Listen to events on a specific instance
useGestureViewerEvent('gallery', 'zoomChange', (data) => {
console.log(`Gallery zoom: ${data.scale}x`);
});
return (
<GestureViewer
data={images}
renderItem={renderImage}
/>
);
}
Event | Description | 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 } |
You can customize the styling of GestureViewer
.
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
return (
<GestureViewer
animateBackdrop={false}
width={400}
containerStyle={{ /* ... */ }}
backdropStyle={{ backgroundColor: 'rgba(0, 0, 0, 0.90)' }}
renderContainer={(children) => <View style={{ flex: 1 }}>{children}</View>}
/>
);
}
Property | Description | Default |
---|---|---|
animateBackdrop |
By default, the background opacity gradually decreases from 1 to 0 during downward swipe gestures. When false , this animation is disabled. |
true |
width |
The width of content items. Default is window width. | Dimensions width |
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 |
Allows custom wrapper component around <GestureViewer /> . |
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.
The default
id
value isdefault
.
import { GestureViewer, useGestureViewerController } from 'react-native-gesture-image-viewer';
const firstViewerId = 'firstViewerId';
const secondViewerId = 'secondViewerId';
function App() {
const { currentIndex: firstCurrentIndex, totalCount: firstTotalCount } = useGestureViewerController(firstViewerId);
const { currentIndex: secondCurrentIndex, totalCount: secondTotalCount } = useGestureViewerController(secondViewerId);
return (
<View>
<GestureViewer
id={firstViewerId}
data={images}
renderItem={renderImage}
/>
<GestureViewer
id={secondViewerId}
data={images}
renderItem={renderImage}
/>
</View>
);
}
The onIndexChange
callback function is called when the index
value changes.
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
const [currentIndex, setCurrentIndex] = useState(0);
return (
<GestureViewer
onIndexChange={setCurrentIndex}
/>
);
}
Sets the scroll behavior mode. false
(default) uses paging mode, true
uses snap mode.
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
return (
<GestureViewer
data={data}
renderItem={renderItem}
useSnap={true}
/>
);
}
Sets the spacing between items in pixels. Only applied when useSnap
is true
.
import { GestureViewer } from 'react-native-gesture-image-viewer';
function App() {
return (
<GestureViewer
data={data}
renderItem={renderItem}
useSnap={true}
itemSpacing={16} // 16px spacing between items
/>
);
}
Sets the initial index value.
dismissThreshold
controls when onDismiss
is called by applying a threshold value during vertical gestures.
resistance
controls the range of vertical movement by applying resistance during vertical gestures.
Controls the maximum zoom scale multiplier.
- Wrap the
renderItem
function withuseCallback
to prevent unnecessary re-renders. - For large images, we recommend using
FastImage
orexpo-image
. - For handling many images, we recommend using
FlashList
. - Test on actual devices (performance may be limited in simulators).
For details on how to contribute to the project and set up the development environment, please refer to the Contributing Guide.