Animation
Driving props from the UI thread with shared values.
Props typed Animated<T> accept a Reanimated
SharedValue (SharedValueLike) and
update on the UI thread, with no re-render.
import { useEffect } from 'react'
import { useSharedValue, withRepeat, withTiming } from 'react-native-reanimated'
import { Canvas, Circle } from 'react-native-tgfx'
function Pulse() {
const r = useSharedValue(20)
useEffect(() => {
r.value = withRepeat(withTiming(60, { duration: 900 }), -1, true)
}, [r])
return (
<Canvas style={{ width: 200, height: 200 }}>
<Circle cx={100} cy={100} r={r} color="#d98a4a" />
</Canvas>
)
}Animated colour
Animate colour channels inside a ColorComponents
tuple. A SharedValue holding a colour string or number does not animate.
<Rect x={0} y={0} width={40} height={40} color={[red, 0.2, 0.4, 1]} />Clock
const t = useClock()
<Shader source={source} uniforms={{ uTime: t, uSize: [300, 300] }} />One opacity per chain
Animate one opacity per chain. Two opacities multiply and freeze:
[tgfx] two opacities in one chain, at least one animated: a draw command has a
single alpha slot, so their product is frozen at the last commit. Animate one
opacity per chain.Zoom and pan
Pass a SharedValue<Viewport> as viewport to zoom and pan from the UI
thread, with no React render or scene re-upload.
import { Gesture, GestureDetector } from 'react-native-gesture-handler'
import { useSharedValue } from 'react-native-reanimated'
import { Canvas, Circle } from 'react-native-tgfx'
function Map() {
const viewport = useSharedValue({ zoom: 1, x: 0, y: 0 })
// A worklet end to end: no runOnJS, no React render, no scene re-encode.
const pan = Gesture.Pan().onChange(e => {
const at = viewport.value
viewport.value = {
zoom: at.zoom,
x: at.x + e.changeX,
y: at.y + e.changeY,
}
})
return (
<GestureDetector gesture={pan}>
<View style={{ height: 200 }} collapsable={false}>
<Canvas viewport={viewport} style={{ flex: 1 }}>
<Circle cx={100} cy={100} r={40} color="#d98a4a" />
</Canvas>
</View>
</GestureDetector>
)
}Write the whole object at once. zoom scales about the origin, then x/y
pan. To pinch about the focal point:
const pinch = Gesture.Pinch().onChange(e => {
const at = viewport.value
const next = Math.max(0.5, Math.min(8, at.zoom * e.scaleChange))
const sceneX = (e.focalX - at.x) / at.zoom
const sceneY = (e.focalY - at.y) / at.zoom
viewport.value = {
zoom: next,
x: e.focalX - next * sceneX,
y: e.focalY - next * sceneY,
}
})Without Reanimated, call CanvasHandle.setViewport() from a JS gesture
callback. Headless, use
surface.setViewport([zoom, x, y]).
hitTest and snapshot() follow the viewport. <CameraFrame> does not; wrap
it in a <Group transform> to zoom it. Use the viewport prop or
setViewport, not both. Invalid values are ignored.
Without Reanimated
useNode works from plain JS handlers. With
Reanimated, writes also run in worklets (Imperative), and
createScene builds scenes on a worklet runtime.