Skip to main content

Zooming

Native Zoom Gesture

The <Camera> component already provides a natively implemented zoom gesture which you can enable with the enableZoomGesture prop. If you don't need any additional logic in your zoom gesture, you can skip to the next section.

🚀 Next section: Focusing

If you want to setup a custom gesture, such as the one in Snapchat or Instagram where you move up your finger while recording, first understand how zoom is expressed.

Min, Max and Neutral Zoom

A Camera device has different minimum, maximum and neutral zoom values. Those values are expressed through the CameraDevice's minZoom, maxZoom and neutralZoom props, and are represented in "scale". So if the maxZoom property of a device is 2, that means the view can be enlarged by twice it's zoom, aka the viewport halves.

  • The minZoom value is always 1.
  • The maxZoom value can have very high values (such as 128), but often you want to clamp this value to something realistic like 16.
  • The neutralZoom value is often 1, but can be larger than 1 for devices with "fish-eye" (ultra-wide-angle) cameras. In those cases, the user expects to be at whatever zoom value neutralZoom is (e.g. 2) per default, and if he tries to zoom out even more, he goes to minZoom (1), which switches over to the "fish-eye" (ultra-wide-angle) camera as seen in this GIF:

The Camera's zoom property expects values to be in the same "factor" scale as the minZoom, neutralZoom and maxZoom values - so if you pass zoom={device.minZoom} it is at the minimum available zoom, where as if you pass zoom={device.maxZoom} the maximum zoom value possible is zoomed in. It is recommended that you start at device.neutralZoom and let the user manually zoom out to the fish-eye camera on demand (if available).

Logarithmic scale

A Camera's zoom property is represented in a logarithmic scale. That means, increasing from 1 to 2 will appear to be a much larger offset than increasing from 127 to 128. If you want to implement a zoom gesture (<PinchGestureHandler>, <PanGestureHandler>), try to flatten the zoom property to a linear scale by raising it exponentially. (zoom.value ** 2)

Pinch-to-zoom

The above example only demonstrates how to animate the zoom property. To actually implement pinch-to-zoom or pan-to-zoom, take a look at the VisionCamera example app, the pinch-to-zoom gesture can be found here, and the pan-to-zoom gesture can be found here. They implement a real world use-case, where the maximum zoom value is clamped to a realistic value, and the zoom responds very gracefully by using a logarithmic scale.

Example (Reanimated + Gesture Handler)

While you can use any animation library to animate the zoom property (or use no animation library at all) it is recommended to use react-native-reanimated to achieve best performance. Head over to their Installation guide to install Reanimated if you haven't already.

Overview

  1. Make the Camera View animatable using createAnimatedComponent
  2. Make the Camera's zoom property animatable using addWhitelistedNativeProps
  3. Create a SharedValue using useSharedValue which represents the zoom state (from 0 to 1)
  4. Use useAnimatedProps to map the zoom SharedValue to the zoom property.
  5. We apply the animated props to the ReanimatedCamera component's animatedProps property.

Code

The following example implements a pinch-to-zoom gesture using react-native-gesture-handler and react-native-reanimated:

import { Camera, useCameraDevice, CameraProps } from 'react-native-vision-camera'
import Reanimated, { useAnimatedProps, useSharedValue } from 'react-native-reanimated'
import { Gesture, GestureDetector } from 'react-native-gesture-handler'

Reanimated.addWhitelistedNativeProps({
zoom: true,
})
const ReanimatedCamera = Reanimated.createAnimatedComponent(Camera)

export function App() {
const device = useCameraDevice('back')
const zoom = useSharedValue(device.neutralZoom)

const zoomOffset = useSharedValue(0);
const gesture = Gesture.Pinch()
.onBegin(() => {
zoomOffset.value = zoom.value
})
.onUpdate(event => {
const z = zoomOffset.value * event.scale
zoom.value = interpolate(
z,
[1, 10],
[device.minZoom, device.maxZoom],
Extrapolation.CLAMP,
)
})

const animatedProps = useAnimatedProps<CameraProps>(
() => ({ zoom: zoom.value }),
[zoom]
)

if (device == null) return <NoCameraDeviceError />
return (
<GestureDetector gesture={gesture}>
<ReanimatedCamera
style={StyleSheet.absoluteFill}
device={device}
isActive={true}
animatedProps={animatedProps}
/>
</GestureDetector>
)
}

You can also use Gesture Handler to implement different zoom gestures, such as the slide-up-to-zoom as seen in Instagram or Snapchat, or a slider as seen in the stock iOS Camera app.


🚀 Next section: Focusing