105 lines
5.5 KiB
Plaintext
105 lines
5.5 KiB
Plaintext
---
|
|
id: animated
|
|
title: Zooming
|
|
sidebar_label: Zooming
|
|
---
|
|
|
|
import useBaseUrl from '@docusaurus/useBaseUrl';
|
|
|
|
<div>
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="283" height="535" style={{ float: 'right' }}>
|
|
<image href={useBaseUrl("img/demo.gif")} x="18" y="33" width="247" height="469" />
|
|
<image href={useBaseUrl("img/frame.png")} width="283" height="535" />
|
|
</svg>
|
|
</div>
|
|
|
|
The `<Camera>` component already provides a natively implemented zoom gesture which you can enable with the [`enableZoomGesture`](/docs/api/interfaces/cameraprops.cameraprops-1#enablezoomgesture) prop. This does not require any additional work, but if you want to setup a custom gesture, such as the one in Snapchat or Instagram where you move up your finger while recording, continue reading.
|
|
|
|
### Animation libraries
|
|
|
|
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](https://github.com/software-mansion/react-native-reanimated) (v2) to achieve best performance. Head over to their [Installation guide](https://docs.swmansion.com/react-native-reanimated/docs/installation) to install Reanimated if you haven't already.
|
|
|
|
### Implementation
|
|
|
|
#### Overview
|
|
|
|
1. Make the Camera View animatable using `Reanimated.createAnimatedComponent`
|
|
2. Make the Camera's `zoom` property animatable using `addWhitelistedNativeProps`
|
|
> Note that this might not be needed in the future, see: [reanimated#1409](https://github.com/software-mansion/react-native-reanimated/pull/1409)
|
|
3. Create a SharedValue using [`useSharedValue`](https://docs.swmansion.com/react-native-reanimated/docs/api/useSharedValue) which represents the zoom state (from `0` to `1`)
|
|
4. Use [`useAnimatedProps`](https://docs.swmansion.com/react-native-reanimated/docs/api/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 button which smoothly zooms to a random value using [react-native-reanimated](https://github.com/software-mansion/react-native-reanimated):
|
|
|
|
```tsx
|
|
import Reanimated, {
|
|
useAnimatedProps,
|
|
useSharedValue,
|
|
withSpring,
|
|
} from "react-native-reanimated"
|
|
|
|
const ReanimatedCamera = Reanimated.createAnimatedComponent(Camera)
|
|
Reanimated.addWhitelistedNativeProps({
|
|
zoom: true,
|
|
})
|
|
|
|
export function App() {
|
|
const devices = useCameraDevices()
|
|
const device = devices.back
|
|
const zoom = useSharedValue(0)
|
|
|
|
const onRandomZoomPress = useCallback(() => {
|
|
zoom.value = withSpring(Math.random())
|
|
}, [])
|
|
|
|
const animatedProps = useAnimatedProps<Partial<CameraProps>>(
|
|
() => ({ zoom: zoom.value }),
|
|
[zoom]
|
|
)
|
|
|
|
if (device == null) return <LoadingView />
|
|
return (
|
|
<>
|
|
<ReanimatedCamera
|
|
style={StyleSheet.absoluteFill}
|
|
device={device}
|
|
isActive={true}
|
|
animatedProps={animatedProps}
|
|
/>
|
|
<TouchableOpacity
|
|
style={styles.zoomButton}
|
|
onPress={onRandomZoomPress}>
|
|
<Text>Zoom randomly!</Text>
|
|
</TouchableOpacity>
|
|
</>
|
|
)
|
|
}
|
|
```
|
|
|
|
### 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`](/docs/api/interfaces/cameradevice.cameradevice-1#minzoom), [`maxZoom`](/docs/api/interfaces/cameradevice.cameradevice-1#maxzoom) and [`neutralZoom`](/docs/api/interfaces/cameradevice.cameradevice-1#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 smaller 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:
|
|
|
|
<div align="center">
|
|
<img src="https://developer.android.com/images/training/camera/multi-camera-4.gif" width="45%" />
|
|
</div>
|
|
|
|
### Logarithmic scale
|
|
|
|
A Camera's `zoom` property is represented in a **logarithmic scale**. That means, increasing from `0` to `0.1` will appear to be a much larger offset than increasing from `0.9` to `1`. 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](https://github.com/cuvent/react-native-vision-camera/tree/main/example), the pinch-to-zoom gesture can be found [here](https://github.com/cuvent/react-native-vision-camera/blob/d8551792e97eaa6fa768f54059ffce054bf748d9/example/src/CameraPage.tsx#L162-L176), and the pan-to-zoom gesture can be found [here](https://github.com/cuvent/react-native-vision-camera/blob/d8551792e97eaa6fa768f54059ffce054bf748d9/example/src/views/CaptureButton.tsx#L185-L205). 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.
|
|
|
|
<br />
|
|
|
|
#### 🚀 Next section: [Focusing](focusing)
|