Drawing
Shapes, paint, groups and hit testing.
Draw in a Canvas, or without React, in a
Scene:
import { Canvas, Rect } from 'react-native-tgfx'
<Canvas style={{ width: 300, height: 140 }}>
<Rect x={0} y={0} width={300} height={140} color="#101725" />
</Canvas>Each component has a lowercase factory (Rect → rect). See
Imperative.
Shapes
| Component | Props |
|---|---|
Rect | RectProps |
RoundedRect | RoundedRectProps |
Circle | CircleProps |
Oval | OvalProps |
Line | LineProps |
Path | PathProps |
Fill | fills the clip |
Atlas | AtlasProps |
Vertices | VerticesProps |
Paint
Props inline, or a Paint child (PaintProps):
<Circle cx={70} cy={70} r={40}>
<Paint color="#f2c088" style="stroke" strokeWidth={3} />
</Circle>Color accepts names, hex, rgb()/rgba(),
hsl()/hsla(), 0xAARRGGBB, [r, g, b, a] in 0..1, and color():
<Rect color="color(display-p3 1 0.2 0.1)" />
<Rect color="color(srgb 1 0 0 / 0.5)" />Group
GroupProps. Paint and Transform inherit down.
<Group transform={[{ translate: [40, 0] }, { rotate: '12deg' }, { scale: 1.5 }]}>
<Rect x={0} y={0} width={40} height={40} />
</Group><Group clip={{ x: 0, y: 0, width: 100, height: 100, r: 12 }}>
<TgfxImage x={0} y={0} width={100} height={100} image={photo} />
</Group>clip takes a ClipRect or SVG path data.
invertClip keeps the outside instead. origin sets the transform pivot.
layer applies opacity to the group as a whole:
<Group layer opacity={0.5}>
<Circle cx={40} cy={40} r={30} color="#4F8EF7" />
<Circle cx={70} cy={40} r={30} color="#4F8EF7" />
</Group>picture records a static subtree once and replays it.
<Group picture>
<Path path={gridPath} color="#e5e5ea" style="stroke" />
</Group>3D transforms
transform also takes perspective, rotateX, rotateY and translateZ.
Put perspective before the rotations it applies to.
preserve3d sorts child groups by depth. clip, layer, picture,
<FilmGrain> or an export flattens it.
<Group
preserve3d
transform={[{ translate: [150, 150] }, { perspective: 900 }, { rotateY: turn }]}
>
<Group transform={[{ translateZ: 50 }]}>
<Rect x={-50} y={-50} width={100} height={100} color="#0a84ff" />
</Group>
<Group transform={[{ rotateY: '90deg' }, { translateZ: 50 }]}>
<Rect x={-50} y={-50} width={100} height={100} color="#ff3b30" />
</Group>
</Group>Atlas and meshes
<Atlas
image={sheet}
sprites={particles.map((p) => ({
rect: { x: 0, y: 0, width: 16, height: 16 },
x: p.x,
y: p.y,
scale: p.size,
}))}
/><Vertices
vertices={[
{ x: 0, y: 0 },
{ x: 200, y: 0 },
{ x: 0, y: 200 },
{ x: 200, y: 200 },
]}
colors={['#ff0000', '#00ff00', '#0000ff', '#ffff00']}
indices={[0, 1, 2, 1, 2, 3]}
/>Viewport
<Canvas viewport={{ zoom, x, y }}> zooms and pans the whole scene. For
gestures, use CanvasHandle.setViewport; see
Zoom and pan. Headless, use
surface.setViewport.
Hit testing
CanvasHandle.hitTest returns HitTestResult topmost first.
const canvas = useRef<CanvasHandle>(null)
const bar = useNode()
<GestureDetector gesture={Gesture.Tap().onEnd((e) => {
const hits = canvas.current?.hitTest(e.x, e.y) ?? []
if (hits.some((hit) => hit.node === bar)) select(bar)
})}>
<Canvas ref={canvas} style={{ flex: 1 }}>
<Rect ref={bar} x={10} y={10} width={40} height={80} color="#0a84ff" />
</Canvas>
</GestureDetector>useCanvasPress does the same with the responder system:
const onTouch = useCanvasPress(canvas, [[bar, () => select(bar)]], {
precise: true,
onPress: ({ x, y, nodes }) => track(x, y, nodes),
})
<View onStartShouldSetResponder={() => true} onResponderRelease={onTouch}>
<Canvas ref={canvas}>
<Rect ref={bar} x={10} y={10} width={40} height={80} color="#0a84ff" />
</Canvas>
</View>Options are CanvasPressOptions.
precise tests the filled shape, not its bounds. onPress gets a
CanvasPress for every press, even on empty space.
Headless, use dispatchPress with Scene.handlesAt:
dispatchPress(scene.handlesAt(surface.hitTest(x, y, true)), [
[bar, () => select(bar)],
])