Skip to content

kesha-antonov/react-native-zoom-reanimated

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

167 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

npm version npm downloads license platforms TypeScript

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.


✨ 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
  • πŸ“ TypeScript β€” Complete type definitions included

πŸ“Έ Preview

iOS Android
iOS.mov
Android.mp4

πŸ“– Table of Contents

πŸ“‹ Requirements

Dependency Version
react-native-reanimated >= 2.0.0
react-native-gesture-handler >= 2.0.0

πŸ“¦ Installation

Install the library using either Yarn:

yarn add react-native-zoom-reanimated

or npm:

npm install --save react-native-zoom-reanimated

Make sure you have react-native-reanimated and react-native-gesture-handler installed and configured.

πŸš€ Usage

import Zoom from 'react-native-zoom-reanimated'

// For Apple Photos-style gallery, also import ScrollableRef type
import Zoom, { ScrollableRef } from 'react-native-zoom-reanimated'

πŸ’‘ Examples

πŸ“ See the example/ directory for complete working examples.

Basic Usage

import Zoom from 'react-native-zoom-reanimated'

<Zoom>
  <Image
    source={{ uri: imageUri }}
    resizeMode="contain"
    style={{ width: deviceWidth, height: imageHeight * deviceWidth / imageWidth }}
  />
</Zoom>

Image Gallery with FlatList

Basic horizontal gallery with paging:

<FlatList
  data={IMAGES}
  horizontal
  pagingEnabled
  renderItem={({ item }) => (
    <View style={{ width: screenWidth }}>
      <Zoom>
        <Image source={{ uri: item }} style={{ width: '100%', height: '100%' }} resizeMode="contain" />
      </Zoom>
    </View>
  )}
/>

πŸ“„ Full example: example/ImageGalleryStandalone.tsx

Apple Photos-Style Gallery

For seamless swipe navigation while zoomed β€” just like Apple Photos:

<Zoom
  enableGallerySwipe
  parentScrollRef={flatListRef}
  currentIndex={index}
  itemWidth={deviceWidth + IMAGE_GAP}
>
  <Image source={{ uri: imageUri }} />
</Zoom>

Features:

  • βœ… Swipe between images even while zoomed in
  • βœ… Smooth edge-to-scroll transition
  • βœ… Auto zoom reset when changing images
  • βœ… Gap between images

πŸ“„ Full example: example/FlatListExample.tsx β€” complete implementation with all features

Using the Hook Directly

For advanced control, use useZoomGesture hook:

import { useZoomGesture } from 'react-native-zoom-reanimated'
import { useAnimatedReaction } from 'react-native-reanimated'

const { zoomGesture, contentContainerAnimatedStyle, onLayout, onLayoutContent, zoomOut, isZoomedIn, scale } = useZoomGesture({
  minScale: 1,
  maxScale: 5,
})

// React to scale changes efficiently in worklet (no JS bridge overhead)
useAnimatedReaction(
  () => scale.value,
  (currentScale) => {
    console.log('Current scale:', currentScale)
  }
)

// React to zoom state changes
useAnimatedReaction(
  () => isZoomedIn.value,
  (isZoomed) => {
    console.log('Is zoomed:', isZoomed)
  }
)

πŸ“„ Full example: example/UseZoomGestureExample.tsx

πŸ“– API Reference

Zoom Component Props

Name Type Required Description
style StyleProp<ViewStyle> No Container style
contentContainerStyle StyleProp<ViewStyle> No Content container style
minScale number No 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
animationFunction function No Animation function from react-native-reanimated. Default: withTiming. For example, you can use withSpring instead: https://docs.swmansion.com/react-native-reanimated/docs/api/animations/withSpring
animationConfig object No Config for animation function from react-native-reanimated. For example, avaiable options for withSpring animation: https://docs.swmansion.com/react-native-reanimated/docs/api/animations/withSpring#options-object
doubleTapConfig DoubleTapConfig No Config for zoom on double tap. See below for details

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:

interface ScrollableRef {
  scrollToOffset?: (params: { offset: number; animated?: boolean }) => void  // FlatList
  scrollTo?: (params: { x?: number; y?: number; animated?: boolean }) => void // ScrollView
}

πŸ”§ Advanced Usage: useZoomGesture Hook

For advanced use cases, use the useZoomGesture hook directly for full control.

πŸ“„ See example/UseZoomGestureExample.tsx for a complete example.

Zoom Component vs useZoomGesture Hook

Approach Simplicity Performance When to use
Zoom + onZoomStateChange/onZoomChange βœ… Simple ⚠️ Via JS bridge Most use cases
useZoomGesture + useAnimatedReaction ⚠️ More complex βœ… 120fps, no bridge Performance-critical apps

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)
}

Basic Hook Usage

import { useZoomGesture } from 'react-native-zoom-reanimated'
import { GestureDetector } from 'react-native-gesture-handler'
import Animated from 'react-native-reanimated'

function MyCustomZoomComponent() {
  const {
    zoomGesture,
    contentContainerAnimatedStyle,
    onLayout,
    onLayoutContent,
    zoomOut,
    isZoomedIn,
  } = useZoomGesture({
    doubleTapConfig: { defaultScale: 3, minZoomScale: 1, maxZoomScale: 10 },
  })

  return (
    <GestureDetector gesture={zoomGesture}>
      <View onLayout={onLayout}>
        <Animated.View style={contentContainerAnimatedStyle} onLayout={onLayoutContent}>
          {/* Your zoomable content */}
        </Animated.View>
      </View>
    </GestureDetector>
  )
}

πŸ“¦ 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.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes
  4. Run validation (yarn tsc --noEmit && yarn eslint src/)
  5. Commit your changes (git commit -m 'Add amazing feature')
  6. Push to the branch (git push origin feature/amazing-feature)
  7. Open a Pull Request

πŸ‘₯ Author

Maintained by Kesha Antonov

Please note that this project is maintained in free time. If you find it helpful, please consider becoming a sponsor.

πŸ“„ License

MIT

About

Smooth pinch-to-zoom component for React Native with Apple Photos-style gestures and rubber-band physics

Resources

License

Stars

Watchers

Forks

Sponsor this project

 

Packages

 
 
 

Contributors