Imperative
Node handles, slot writes and buffers.
useNode returns a NodeHandle.
set() updates a mounted node without a React render.
import { Canvas, RoundedRect, useNode } from 'react-native-tgfx'
function Box() {
const box = useNode()
return (
<>
<Canvas style={{ width: 300, height: 200 }}>
<RoundedRect ref={box} x={20} y={20} width={70} height={50} r={12} color="#4F8EF7" />
</Canvas>
<Button title="move" onPress={() => box.set({ x: 120 })} />
</>
)
}Colour from a worklet
box.set({ color: '#4F8EF7' }) // JS thread only - the parser lives there
box.set({ color: 0xff4f8ef7 }) // packed AARRGGBB, worklet-safe
box.set({ color: [0.3, 0.5, 1, 1] }) // r, g, b, a in 0..1, worklet-safeGestures
const pan = Gesture.Pan().onUpdate((e) => {
'worklet'
dot.set({ cx: e.x, cy: e.y })
})To drive the canvas viewport from a gesture,
pass it a SharedValue<Viewport>:
const viewport = useSharedValue({ zoom: 1, x: 0, y: 0 })
const zoom = Gesture.Pinch().onChange((e) => {
const at = viewport.value
viewport.value = { ...at, zoom: at.zoom * e.scaleChange }
})
// <Canvas viewport={viewport}>CanvasHandle.setViewport() does the same from JS.
What has slots
Geometry, transforms, paint, images, uniforms, gradients, blur, shadow, alpha
threshold, backdrop, film grain, text, paths and visible patch in place. Other
props re-encode. Max 16 gradient stops and 32 uniform floats.
fx.set({ uniforms: { uFace: [face.x, face.y], uRadius: face.width / 2 } })
label.set({ text: String(Math.round(fps.value)) })
sprite.set({ rect: { x: frame * 32, y: 0, width: 32, height: 32 } })
fade.set({ end: { x: drag.x, y: 0 }, positions: [0, t] })
glow.set({ blur: 4 + press.value * 12, dy: 2 + press.value * 6 })Pools
createHandle is useNode without the hook.
visible={false} skips drawing and hit testing.
const [dots] = useState(() => Array.from({ length: 64 }, () => createHandle()))
<Canvas>
{dots.map((ref, i) => (
<Circle key={i} ref={ref} cx={0} cy={0} r={4} visible={false} color="#fff" />
))}
</Canvas>
dots[next].set({ visible: true, cx: x, cy: y })Scenes without React
Each component has a factory (rect,
circle, group,
linearGradient, mask
and so on). A Scene draws a tree of them without React.
Both forms compile the same program:
import { Canvas, Circle, Group, LinearGradient, Rect, useNode } from 'react-native-tgfx'
function Scene() {
const box = useNode()
return (
<Canvas style={{ flex: 1 }}>
<Rect ref={box} x={0} y={0} width={40} height={40} color="tomato" />
<Circle cx={100} cy={100} r={20}>
<LinearGradient start={{ x: 80, y: 80 }} end={{ x: 120, y: 120 }} colors={['#000', '#fff']} />
</Circle>
<Group transform={[{ rotate: 0.3 }]}>
<Rect x={-20} y={-20} width={40} height={40} color="gold" />
</Group>
</Canvas>
)
}
// later, from JS or a worklet - no React render
box.set({ x: 120 })add, insert, remove and replaceChildren batch into one compile per
microtask; flush() commits now. Update values through handles. One scene can
drive several targets.
Where slot writes go
set() writes shared native memory and allocates nothing. Last write wins until
the next React commit.
Build a scene in a worklet with createScene() (Worklets).
Buffers
useSpriteBuffer and
useVertexBuffer share a typed array with the
renderer (SpriteBuffer, VertexBuffer).
const sprites = useSpriteBuffer(512) // SPRITE_FLOATS per sprite
const count = useSharedValue(0)
<Atlas image={sheet} buffer={sprites} count={count} />
// per frame, in a worklet: srcX, srcY, srcW, srcH, scale, centreX, centreY
sprites.set(i, 0, 0, 16, 16, p.scale, p.x, p.y)
count.value = alive // set the count after filling entriesThe canvas redraws only when a bound shared value changes. For a constant count, publish
count.value = N + (frame.timestamp % 1000) * 1e-6 (truncated natively).
For speed, write the Float32Array from spriteFloats:
const f = spriteFloats(sprites) // SPRITE_FLOATS per sprite
for (let i = 0; i < alive; i++) {
const o = i * SPRITE_FLOATS
f[o + 4] = p[i].scale
f[o + 5] = p[i].x
f[o + 6] = p[i].y
}useVertexBuffer takes a VertexBufferOptions.
Write its positions/textures/colors/indices views from JS:
const mesh = useVertexBuffer({ maxVertices: 4, maxIndices: 6 })
mesh.positions.set([0, 0, 200, 0, 0, 200, 200, 200])
mesh.indices.set([0, 1, 2, 1, 2, 3])
<Vertices buffer={mesh} vertexCount={4} indexCount={6} />In a worklet, write through meshViews:
const drag = Gesture.Pan().onUpdate((e) => {
'worklet'
meshViews(mesh).positions[0] = e.x
})