react-native-image-pan-zoom and react-native-image-zoom-viewer are both React Native libraries designed to handle image manipulation gestures like pinch-to-zoom, pan, and double-tap zoom. react-native-image-pan-zoom focuses on providing a low-level component that wraps images to enable gesture handling, often giving developers more control over the transformation matrix and animation physics. react-native-image-zoom-viewer is a higher-level solution that often includes a viewer interface with support for multiple images, saving to gallery, and built-in loading states, aiming for a quicker implementation of gallery-like experiences.
When building mobile apps that involve media, users expect smooth pinch-to-zoom and pan gestures. Both react-native-image-pan-zoom and react-native-image-zoom-viewer solve this, but they target different layers of the application architecture. One acts as a primitive building block, while the other functions as a complete solution.
react-native-image-pan-zoom provides a wrapper component that makes its children zoomable.
// react-native-image-pan-zoom: Wraps any content
import ImageZoom from 'react-native-image-pan-zoom';
import { Image } from 'react-native';
const CustomZoom = () => (
<ImageZoom cropWidth={Dimensions.get('window').width} cropHeight={Dimensions.get('window').height}>
<Image source={{ uri: 'https://example.com/img.jpg' }} style={{ width: 300, height: 300 }} />
</ImageZoom>
);
react-native-image-zoom-viewer is designed specifically for viewing images in a modal or full-screen context.
// react-native-image-zoom-viewer: Dedicated image viewer
import ImageViewer from 'react-native-image-zoom-viewer';
const GalleryViewer = () => {
const images = [{ url: 'https://example.com/img1.jpg' }, { url: 'https://example.com/img2.jpg' }];
return <ImageViewer imageUrls={images} />;
};
react-native-image-pan-zoom exposes callbacks for gesture events, allowing you to hook into the pan and zoom lifecycle.
// react-native-image-pan-zoom: Access gesture state
<ImageZoom
onMove={(data) => console.log('Pan position:', data.moveX, data.moveY)}
onZoom={(data) => console.log('Current scale:', data.scale)}
>
<Image source={...} />
</ImageZoom>
react-native-image-zoom-viewer abstracts gesture logic to provide a polished UX out of the box.
// react-native-image-zoom-viewer: Limited gesture hooks
<ImageViewer
onSwipeDown={() => console.log('Swipe down to close detected')}
onChange={(index) => console.log('Image index changed:', index)}
// No direct access to raw pinch scale values during gesture
/>
react-native-image-pan-zoom handles one content child at a time.
// react-native-image-pan-zoom: Manual gallery implementation
const [currentIndex, setCurrentIndex] = useState(0);
<ImageZoom>
<Image source={images[currentIndex]} />
</ImageZoom>
// You must implement Swipeable or FlatList logic externally to change currentIndex
react-native-image-zoom-viewer supports multiple images natively via the imageUrls prop.
// react-native-image-zoom-viewer: Native gallery support
const images = [
{ url: 'https://example.com/1.jpg' },
{ url: 'https://example.com/2.jpg' }
];
<ImageViewer imageUrls={images} index={0} />;
// Swiping is handled internally
react-native-image-pan-zoom requires explicit definition of the crop area.
cropWidth and cropHeight usually derived from window dimensions.// react-native-image-pan-zoom: Explicit dimensions required
const { width, height } = Dimensions.get('window');
<ImageZoom cropWidth={width} cropHeight={height} imageWidth={300} imageHeight={300}>
<Image source={...} />
</ImageZoom>
react-native-image-zoom-viewer attempts to auto-detect dimensions but allows overrides.
// react-native-image-zoom-viewer: Flexible but opinionated layout
<ImageViewer
imageUrls={images}
useSwipeNum={2} // Configure swipe sensitivity
enableSwipeDown={true} // Enable dismiss gesture
/>
react-native-image-pan-zoom has historically been stable but sees infrequent updates.
react-native-image-zoom-viewer has faced maintenance challenges in the past with React Native version upgrades.
Despite their architectural differences, both libraries solve the core problem of touch interaction for media.
// react-native-image-pan-zoom
<ImageZoom maxOverflow={100} minScale={0.5} maxScale={3}>
<Image source={...} />
</ImageZoom>
// react-native-image-zoom-viewer
<ImageViewer maxScale={4} minScale={1} imageUrls={...} />
// react-native-image-pan-zoom
<ImageZoom enableDoubleClickZoom={true} />
// react-native-image-zoom-viewer
<ImageViewer enableDoubleClickZoom={true} />
// react-native-image-pan-zoom
// Render custom loader inside the Image component before source loads
<Image source={...} onLoadStart={() => setLoading(true)} />
// react-native-image-zoom-viewer
<ImageViewer
renderImage={(props) => <CustomImageComponent {...props} />}
renderIndicator={() => <CustomIndicator />}
/>
| Feature | react-native-image-pan-zoom | react-native-image-zoom-viewer |
|---|---|---|
| Primary Use | Embeddable zoomable area | Full-screen image gallery |
| Content Type | Any View (Image, Map, SVG) | Images only |
| Gallery Logic | Manual implementation required | Built-in swipe navigation |
| Layout Control | High (define crop area) | Low (expects full viewport) |
| Setup Complexity | Higher (more props to configure) | Lower (pass array of URLs) |
react-native-image-pan-zoom is like a raw engine ποΈβit gives you the power to build a custom driving experience. Use it when the image is just one part of a complex screen, like a product detail page where you also need to interact with buttons and text around the zoomable area.
react-native-image-zoom-viewer is like a rideshare service πβit gets you from A to B with minimal effort. Use it when you need a standard photo gallery quickly and don't want to maintain custom gesture logic.
Final Thought: If you are building a dedicated photo app, react-native-image-zoom-viewer saves weeks of work. If you are building a dashboard with zoomable charts or maps, react-native-image-pan-zoom is the only viable option.
Choose react-native-image-pan-zoom if you need fine-grained control over gesture physics and transformation matrices within a specific layout. It is better suited for scenarios where the image is part of a larger interactive interface, such as a map annotation or a custom editor, rather than a full-screen gallery. This package is ideal when you want to build custom UI around the zoomable area without being locked into a predefined viewer structure.
Choose react-native-image-zoom-viewer if your goal is to implement a full-screen image gallery with minimal setup. It is the preferred choice for standard photo viewing workflows where features like swipe navigation between multiple images, saving to the device, and built-in loading indicators are required out of the box. Use this when development speed and standard user experience patterns take priority over custom gesture tuning.
Zoom while sliding

Intelligent zoom

npm i react-native-image-pan-zoom --save
$ npm install -g create-react-native-app
$ create-react-native-app AwesomeProject
AwesomeProject/App.js, like this:import { Image, Dimensions } from 'react-native';
import ImageZoom from 'react-native-image-pan-zoom';
export default class App extends React.Component {
render: function() {
return (
<ImageZoom cropWidth={Dimensions.get('window').width}
cropHeight={Dimensions.get('window').height}
imageWidth={200}
imageHeight={200}>
<Image style={{width:200, height:200}}
source={{uri:'http://v1.qzone.cc/avatar/201407/07/00/24/53b9782c444ca987.jpg!200x200.jpg'}}/>
</ImageZoom>
)
}
}
| Props | Type | Description | DefaultValue |
|---|---|---|---|
| cropWidth(required) | number | operating area width | 100 |
| cropHeight(required) | number | operating area height | 100 |
| imageWidth(required) | number | picture width | 100 |
| imageHeight(required) | number | picture height | 100 |
| onClick | (eventParams: IOnClick)=>void | onClick | ()=>{} |
| onDoubleClick | (eventParams: IOnClick)=>void | onDoubleClick | ()=>{} |
| panToMove | boolean | allow to move picture with one finger | true |
| pinchToZoom | boolean | allow scale with two fingers | true |
| clickDistance | number | how many finger movement can also trigger onClick | 10 |
| horizontalOuterRangeOffset | (offsetX?: number)=>void | horizontal beyond the distance, the parent to do picture switching, you can listen to this function. When this function is triggered, you can do the switch operation | ()=>{} |
| onDragLeft | ()=>void | trigger to switch to the left of the graph, the left sliding speed exceeds the threshold when triggered | ()=>{} |
| responderRelease | (vx: number)=>void | let go but do not cancel | ()=>{} |
| maxOverflow | number | maximum sliding threshold | 100 |
| longPressTime | number | long press threshold | 800 |
| onLongPress | (eventParams: IOnClick)=>void | on longPress | ()=> {} |
| doubleClickInterval | number | time allocated for second click to be considered as doublClick event | 175 |
| onMove | ( position: IOnMove )=>void | reports movement position data (helpful to build overlays) | ()=> {} |
| centerOn | { x: number, y: number, scale: number, duration: number } | if given this will cause the map to pan and zoom to the desired location | undefined |
| enableSwipeDown | boolean | for enabling vertical movement if user doesn't want it | false |
| enableCenterFocus | boolean | for disabling focus on image center if user doesn't want it | true |
| onSwipeDown | () => void | function that fires when user swipes down | null |
| swipeDownThreshold | number | threshold for firing swipe down function | 230 |
| minScale | number | minimum zoom scale | 0.6 |
| maxScale | number | maximum zoom scale | 10 |
| useNativeDriver | boolean | Whether to animate using useNativeDriver | false |
| onStartShouldSetPanResponder | () => boolean | Override onStartShouldSetPanResponder behavior | () => true |
| onMoveShouldSetPanResponder | () => boolean | Override onMoveShouldSetPanResponder behavior | undefined |
| onPanResponderTerminationRequest | () => boolean | Override onMoveShouldSetPanResponder behavior | () => false |
| useHardwareTextureAndroid | boolean | for disabling rendering to hardware texture on Android | true |
| Method | params | Description |
|---|---|---|
| reset | Reset the position and the scale of the image | |
| resetScale | Reset the scale of the image | |
| centerOn | ICenterOn | Centers the image in the position indicated. ICenterOn={ x: number, y: number, scale: number, duration: number } |
After clone this repo, then:
npm install
npm start
cd demo
npm install
npm start
Then, scan the QR, use your expo app.
Thanks goes to these wonderful people (emoji key):
Darius π» | Thomas P. π» | Juan Di Toro π» | Alhaytham Elhassan π» | Alexander Pataridze π» | Peter Xu π» |
This project follows the all-contributors specification. Contributions of any kind welcome!