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 always1
. - The
maxZoom
value can have very high values (such as128
), but often you want to clamp this value to something realistic like16
. - The
neutralZoom
value is often1
, but can be larger than1
for devices with "fish-eye" (ultra-wide-angle) cameras. In those cases, the user expects to be at whatever zoom valueneutralZoom
is (e.g.2
) per default, and if he tries to zoom out even more, he goes tominZoom
(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.
Implementation (Reanimated)​
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​
- Make the Camera View animatable using
createAnimatedComponent
- Make the Camera's
zoom
property animatable usingaddWhitelistedNativeProps
- Create a SharedValue using
useSharedValue
which represents the zoom state (from0
to1
) - Use
useAnimatedProps
to map the zoom SharedValue to the zoom property. - We apply the animated props to the
ReanimatedCamera
component'sanimatedProps
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.