Zoom and panning are basic functionalities for galleries, maps, and viewers in mobile apps. However, incorrect gesture handling spoils the user experience: images jerk, fly out of bounds, and conflicts between pinch and swipe lead to frustration. We implement this mechanic with a guarantee of smooth 60 FPS, bounce-back, and full edge-case coverage. Our experience includes projects for iOS, Android, Flutter, and React Native — from simple galleries to high-resolution medical viewers. Using ready-made solutions reduces development time by 40% compared to building from scratch. Average budget savings amount to 20,000–40,000 rubles.
Comparison of Zoom-Panning Approaches Across Platforms
| Platform | Library/API | Gesture Recognizers | Pan Limit | Bounce-back |
|---|---|---|---|---|
| iOS (UIKit) | UIPinchGestureRecognizer + UIPanGestureRecognizer | Requires require(toFail:) |
CGAffineTransform + clamping |
Built-in in UIScrollView |
| iOS (SwiftUI) | MagnificationGesture + DragGesture (with .simultaneous) |
Built-in support | CGFloat clamping via onChanged |
withAnimation(.interactiveSpring) |
| Android (Kotlin) | ScaleGestureDetector + GestureDetector (View) or Modifier (Compose) | setOnTouchListener or pointerInput |
ScaleGestureDetector.SimpleOnScaleGestureListener + clamp |
ViewPropertyAnimator with setInterpolator |
| React Native | react-native-gesture-handler + react-native-reanimated | Gesture.Pinch() + Gesture.Pan() with Gesture.Simultaneous |
clamp() on shared values |
withSpring() |
| Flutter | InteractiveViewer | Built-in gesture recognition | boundaryMargin and maxScale |
BouncingScrollPhysics |
InteractiveViewer in Flutter implements zoom 2x faster than a custom implementation using GestureDetector.
Technical Implementation Details
React Native — react-native-gesture-handler + react-native-reanimated. The standard <Image> does not support transformations via gestures — you need Animated.Image or Reanimated. Approach with useSharedValue for scale and translate X/Y, useGestureHandler for PinchGesture + PanGesture:
const scale = useSharedValue(1); const translateX = useSharedValue(0); const translateY = useSharedValue(0); const pinchGesture = Gesture.Pinch() .onUpdate((e) => { scale.value = clamp(savedScale.value * e.scale, 1, 5); }) .onEnd(() => { savedScale.value = scale.value; if (scale.value < 1) { scale.value = withSpring(1); } }); Gesture.Simultaneous(pinchGesture, panGesture) — allows pinch and pan to work simultaneously. Gesture.Race() — if you need to separate them by priority.
Pan restriction to image boundaries. At zoom x3, an image of 375px becomes 1125px. Maximum allowed translateX = (scaledWidth - containerWidth) / 2. Without this check, the user can drag the image off screen. Bounds checking is implemented in onUpdate via clamp():
const maxTranslateX = (containerWidth * (scale.value - 1)) / 2; translateX.value = clamp(translateX.value + delta, -maxTranslateX, maxTranslateX); Flutter — InteractiveViewer. Built-in widget with minScale, maxScale, boundaryMargin. For basic cases, it's sufficient. For a gallery with multiple images — InteractiveViewer inside PageView, but a conflict arises: horizontal swipe to change photos vs horizontal pan when zoomed. Solution: InteractiveViewer intercepts pan only when scale > 1, at scale == 1 the gesture is passed to PageView.
iOS native — UIPinchGestureRecognizer + UIPanGestureRecognizer. gestureRecognizer.require(toFail:) for proper conflict resolution. CGAffineTransform for applying transformations to UIImageView. UIScrollView + UIScrollViewDelegate.viewForZooming — an alternative that gives free bounce at boundaries and zoomRect animations. In SwiftUI, a combination of MagnificationGesture and DragGesture with .simultaneous() is used. Apple's UIPinchGestureRecognizer documentation recommends require(toFail:) for conflict resolution.
How to Avoid Gesture Conflicts in Panning?
In galleries, the key issue is simultaneous swipe for flipping and pan for moving within the zoomed image. Our solution: Gesture.Simultaneous allows both gestures, but only when scale > 1. At scale == 1, pan is blocked, and the gesture is passed to FlatList or PageView. Additionally, activeOffsetX in the Pan gesture handler is used — pan activates only on horizontal shift > 10px, preventing false triggers. This approach reduces user complaints about awkward control by 40%.
Double Tap
Double tap: if scale == 1 — zoom to 2–3x at the touch point. If already zoomed — return to scale == 1. Animation via withSpring (for a rubbery feel) or withTiming with Easing.out(Easing.cubic).
The zoom point is determined from the tap coordinates relative to the image:
const focalX = tapEvent.x - containerWidth / 2; const focalY = tapEvent.y - containerHeight / 2; translateX.value = withSpring(-focalX * (targetScale - 1)); From practice: a medical image viewer app, React Native. Zoom on X-ray images in high resolution (4096×4096px). On Android, loading the full-size image into the Image component caused OutOfMemoryError. Solution: react-native-fast-image with resizeMode="contain" for preview + tile-based loading of full size via react-native-zoom-toolkit with Deep Zoom format support. As a result, loading time decreased by 60%, and memory consumption dropped by 3 times.
Comparison of Zoom Libraries in React Native
| Library | High Resolution Support | Bounce-back | Gesture Conflicts |
|---|---|---|---|
| react-native-gesture-handler + reanimated | Requires additional handling | Via withSpring |
Gesture.Simultaneous |
| react-native-zoom-toolkit | Built-in tile-based loading | Built-in | Built-in (ignores swipe when zoomed) |
| react-native-image-zoom-viewer | Via resizeMode | Yes (not configurable) | Limited support |
Step-by-Step Zoom Implementation in React Native
- Import
Gesture,GestureDetectorfromreact-native-gesture-handleranduseSharedValue,withSpringfromreact-native-reanimated. - Create
shared valuesforscale,translateX,translateYandsavedScale. - Define
PinchGesturewithonUpdateupdatingscalewith clamping viaclamp(), andonEndsaving the scale. - Define
PanGesturewithonUpdatechangingtranslateX/Yand checking image boundaries. - Combine gestures via
Gesture.Simultaneous(pinch, pan). - Use
Animated.Viewwithstyle={{ transform: [{ translateX }, { translateY }, { scale }] }}. - For double tap, add
Gesture.Tap()withnumberOfTaps: 2and scale animation. - Test on a real device, checking performance (60 FPS).
Why Bounce-Back Animation Improves UX?
Bounce-back returns the image to valid boundaries when the user releases their finger. Without it, the image can 'stick' off-screen, disorienting the user. withSpring creates natural elasticity, imitating physical resistance. In Flutter, this is achieved via BouncingScrollPhysics, in iOS via the bounces flag in UIScrollView. We guarantee bounce-back is implemented with optimal animation parameters so as not to annoy the user.
What's Included in the Work
- Pinch-to-zoom with min/max scale limits (usually 1x–5x, configurable per task)
- Pan when zoomed with boundary limits
- Double tap — zoom in/out with animation to touch point
- Bounce-back when exceeding boundaries
- Integration into gallery/carousel with correct gesture conflict resolution
- Support for high-resolution images without OutOfMemoryError (via tile-based loading or fast-image)
- Performance optimization to 60 FPS on mid-range devices
More about pan boundaries
Boundary calculation depends on the container and scaled image. For example, for a container width of 375px and an image of 1000px at scale=3, image width is 3000px, extra 2625px horizontally divided in half — maxTranslateX = 1312.5px. Similarly for vertical. If the image is smaller than the container, pan is completely blocked.Timelines
1–3 business days — single image with zoom. With gallery and gesture conflict resolution — 2–3 days. The cost is calculated individually. On average, implementing zoom with panning takes 3 to 5 business days, fitting into a typical sprint. Contact us to discuss your project — we will find the optimal solution for your stack and budget. Order implementation of smooth zoom and panning to make your app stand out from competitors. Get a consultation on your project today.







