React Native Zoom Reanimated Apple Photos-style zoom component for React Native with pinch, pan, and double-tap gestures. Built with React Native Reanimated and Gesture Handler for buttery smooth 120fps animations. 📖 Documentation · Installation · Usage · API reference · Gesture Handler v2 / v3 Built and maintained by Kesha Antonov , React Native lead engineer. More…
Apple Photos-style zoom component for React Native with pinch, pan, and double-tap gestures. Built with React Native Reanimated and Gesture Handler for buttery smooth 120fps animations.
cryptoc - my crypto portfolio app. Your coins on the home screen, lock screen and watch face. iPhone, iPad, Mac, Apple Watch, Android, Android tablet and Wear OS.
Portfolio with average buy price and 24h / 180-day / all-time P&L
Widgets in three sizes, refreshed in the background - most days you never open the app
Price alerts on 5,000+ coins, delivered while the app is closed
No account, no email, no exchange API keys, no ads. Your holdings never reach a server - they sync through your own iCloud or Google Drive
Free for 3 holdings, and that is not a trial timer
Downloading it is what pays for the time that goes into these libraries.
✨ Features
🔍 Pinch to Zoom — Smooth pinch gesture with rubber band effect
👆 Double Tap — Tap twice to zoom in/out with configurable scale
🖐️ Pan Gesture — Drag zoomed content with momentum and boundary bounce
📱 Apple Photos Gallery — Seamless swipe between zoomed images in FlatList
🔄 Rubber Band Effect — Natural over-scroll/over-zoom feeling
🎯 Focal Point Zoom — Zoom centers on pinch/tap location
⚡ 120fps — Silky smooth animations on ProMotion displays
import Zoom from'react-native-zoom-reanimated'// For Apple Photos-style gallery, also import ScrollableRef typeimport Zoom,{ ScrollableRef }from'react-native-zoom-reanimated'
💡 Examples
📁 See the example/ directory for complete working examples.
Minimum allowed zoom scale. Default is 1. Set to 1 to prevent zooming out smaller than initial size. Set to a value < 1 (e.g., 0.5) to allow zooming out to 50%
maxScale
number
No
Maximum allowed zoom scale. Default is 4
onZoomStateChange
(isZoomed: boolean) => void
No
Callback fired when zoom state changes. Called with true when zoomed in, false when zoomed out to initial scale
onZoomChange
(scale: number) => void
No
Callback fired during zoom gesture with current scale value. Called continuously while pinching, useful for UI updates (e.g., showing zoom percentage). For performance-critical use cases, use useZoomGesture hook with scale SharedValue instead
enableGallerySwipe
boolean
No
Enable Apple Photos-style seamless gallery navigation. When zoomed and panning hits horizontal boundary, continued swipe allows scrolling to adjacent images. Default is false
parentScrollRef
RefObject<ScrollableRef>
No
Reference to parent FlatList/ScrollView for seamless edge scrolling. When provided with enableGallerySwipe, enables Apple Photos-style continuous swipe: zoomed image pans to edge, then seamlessly scrolls parent list. Compatible with FlatList/ScrollView from react-native, react-native-gesture-handler, and react-native-reanimated
currentIndex
number
No
Current index in the parent list (for calculating scroll offset). Required when using parentScrollRef
itemWidth
number
No
Width of each item in the parent list (for calculating scroll offset). Required when using parentScrollRef. Usually equals deviceWidth + imageGap
Config for zoom on double tap. See below for details
gestureApi
'v2' | 'v3' | 'auto'
No
Which react-native-gesture-handler API to build the gesture with. Default is 'auto', or whatever was passed to setGestureApiVersion(). See Gesture Handler v2 / v3 API
DoubleTapConfig
Name
Type
Required
Description
defaultScale
number
No
Fixed zoom scale on double tap. If not set, calculated based on dimensions
minZoomScale
number
No
Minimum zoom scale for double tap
maxZoomScale
number
No
Maximum zoom scale for double tap
ScrollableRef
Type for parentScrollRef. Compatible with FlatList/ScrollView from multiple libraries:
Zoom component uses callbacks (onZoomChange, onZoomStateChange) that communicate via the JS bridge. This is simple to use but may have slight delays on rapid updates.
useZoomGesture hook returns SharedValue objects (scale, isZoomedIn) that update directly in the UI thread. Use useAnimatedReaction to respond to changes without JS bridge overhead — ideal for 120fps animations.
Hook API
interface UseZoomGestureProps {
animationFunction?: typeof withTiming // Animation function (default: withTiming)
animationConfig?: object // Configuration for animation function
minScale?: number // Minimum allowed zoom scale (default: 1)
maxScale?: number // Maximum allowed zoom scale (default: 4)
enableGallerySwipe?: boolean // Enable Apple Photos-style gallery swipe (default: false)
parentScrollRef?: RefObject<ScrollableRef> // Parent FlatList/ScrollView ref for seamless scrolling
currentIndex?: number // Current index in parent list
itemWidth?: number // Width of each item in parent list
doubleTapConfig?: DoubleTapConfig // Double tap zoom configuration
}
interface UseZoomGestureReturn {
zoomGesture: ComposedGesture // Gesture handler to attach to GestureDetector
contentContainerAnimatedStyle: object // Animated styles for the content container
onLayout: (event: LayoutChangeEvent) => void // Container layout handler
onLayoutContent: (event: LayoutChangeEvent) => void // Content layout handler
zoomOut: () => void // Programmatically zoom out
isZoomedIn: SharedValue<boolean> // Shared value indicating zoom state
zoomGestureLastTime: SharedValue<number> // Timestamp of last gesture interaction
scale: SharedValue<number> // Current zoom scale (use with useAnimatedReaction)
}
react-native-gesture-handler 3.x ships a new hooks-based gestures API (usePanGesture,
usePinchGesture, …) next to the 2.x builder API (Gesture.Pan(), Gesture.Pinch()).
This library builds its zoom gesture with either one.
You do not have to configure anything. By default the API is detected from the
react-native-gesture-handler version installed in your app:
Installed gesture handler
API used
3.x
v3 hooks API
2.x
v2 builder API
Detection happens at both levels: useZoomGesture and <Zoom /> pick the right one at runtime,
and the type of zoomGesture resolves to the matching gesture type at compile time, so it is
always accepted by the GestureDetector of your installed version.
Override it only if you need to - for example to stay on the v2 API after upgrading to
react-native-gesture-handler 3.x.
gestureApi accepts 'auto' (default), 'v2' or 'v3'. Changing it on a mounted Zoom
remounts its gesture tree.
Choosing app-wide
Call setGestureApiVersion once at startup, before rendering any Zoom. It sets the default
used by useZoomGesture and by every Zoom that does not pass gestureApi itself.
import{ setGestureApiVersion }from'react-native-zoom-reanimated'setGestureApiVersion('v2')// or 'v3', or 'auto' (the default)
A component keeps the version it resolved on its first render, so calling this later only
affects new mounts.
Choosing in the hook API
useZoomGesture follows the same auto-detection. To pin a version at a single call site,
import the version-specific hook instead:
import { useZoomGestureV2, useZoomGestureV3 } from 'react-native-zoom-reanimated'
// react-native-gesture-handler v2 builder API - works on 2.x and 3.x
const zoom = useZoomGestureV2({ maxScale: 6 })
// react-native-gesture-handler v3 hooks API - requires 3.x, throws otherwise
const zoom = useZoomGestureV3({ maxScale: 6 })
All three return the same shape; only the type of zoomGesture differs.
Capability check
import{ isGestureApiV3Supported }from'react-native-zoom-reanimated'if(isGestureApiV3Supported()){// react-native-gesture-handler >= 3 is installed}
📦 Example App
cd example
yarn install
yarn start:ios # or yarn start:android
The example app demonstrates:
Basic zoom functionality
Image gallery with FlatList
Apple Photos-style seamless navigation
Using the hook directly
📱 Platform Support
Platform
Status
iOS
✅ Full support
Android
✅ Full support
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Fork the repository
Create your feature branch (git checkout -b feature/amazing-feature)
Make your changes
Run validation (yarn tsc --noEmit && yarn eslint src/)
Commit your changes (git commit -m 'Add amazing feature')
Push to the branch (git push origin feature/amazing-feature)
评论