Canvas (2D) Stable WINMACLNX
A GPU-accelerated 2D drawing surface backed by Vello (opens in a new tab). Draws imperatively through a ref-based context.
Import
import { Canvas } from 'veloxkit'Basic usage
import { Canvas } from 'veloxkit'
import { useRef, useEffect } from 'react'
function MyChart() {
const ctxRef = useRef(null)
useEffect(() => {
const ctx = ctxRef.current
if (!ctx) return
ctx.clear()
ctx.fillStyle = '#00A878'
ctx.fillRect(10, 10, 100, 60)
ctx.strokeStyle = '#F0F0F2'
ctx.lineWidth = 2
ctx.strokeLine(0, 50, 200, 50)
ctx.flush() // send all commands to GPU
}, [])
return <Canvas ref={ctxRef} style={{ width: 300, height: 200 }} />
}Props
| Prop | Type | Description |
|---|---|---|
ref | React.Ref<CanvasContext> | Attach to access the drawing context |
style | ViewStyle | Width and height (required) |
Drawing context (ctx)
Accessed via the ref. All drawing is buffered — call ctx.flush() to submit the commands to the GPU.
Style properties
| Property | Type | Default | Description |
|---|---|---|---|
ctx.fillStyle | string | [r,g,b,a] | [255,255,255,255] | Fill color for shapes and text |
ctx.strokeStyle | string | [r,g,b,a] | [255,255,255,255] | Stroke color |
ctx.lineWidth | number | 1 | Stroke line width in pixels |
Colors accept CSS hex strings ('#ff0000', '#f00') or [r, g, b, a] arrays (0–255).
Methods
| Method | Description |
|---|---|
ctx.clear() | Clear all previous draw commands |
ctx.fillRect(x, y, w, h) | Fill a rectangle |
ctx.strokeRect(x, y, w, h) | Outline a rectangle |
ctx.fillCircle(cx, cy, r) | Fill a circle |
ctx.strokeCircle(cx, cy, r) | Outline a circle |
ctx.strokeLine(x0, y0, x1, y1) | Draw a line segment |
ctx.fillText(text, x, y, fontSize?) | Draw text (default fontSize: 16) |
ctx.flush() | Send buffered commands to the GPU |
⚠️
Always call ctx.flush() after drawing. Without it, nothing appears on screen. clear() wipes the buffer so call it before each full redraw.
Animation
Redraw by calling clear() + draw commands + flush() in a loop. Use setInterval or a requestAnimationFrame-style pattern:
import { Canvas } from 'veloxkit'
import { useRef, useEffect } from 'react'
function AnimatedCircle() {
const ctxRef = useRef(null)
const frameRef = useRef(0)
useEffect(() => {
let running = true
const draw = () => {
const ctx = ctxRef.current
if (!ctx || !running) return
const t = frameRef.current++ / 60
const cx = 150 + Math.sin(t) * 80
const cy = 100 + Math.cos(t * 0.7) * 50
ctx.clear()
ctx.fillStyle = '#0D0D14'
ctx.fillRect(0, 0, 300, 200) // background
ctx.fillStyle = '#7aa2f7'
ctx.fillCircle(cx, cy, 24) // moving ball
ctx.strokeStyle = '#ffffff33'
ctx.lineWidth = 1
ctx.strokeCircle(150, 100, 80) // orbit guide
ctx.flush()
requestAnimationFrame(draw)
}
requestAnimationFrame(draw)
return () => { running = false }
}, [])
return <Canvas ref={ctxRef} style={{ width: 300, height: 200 }} />
}Examples
Bar chart
function BarChart({ data }: { data: number[] }) {
const ctxRef = useRef(null)
useEffect(() => {
const ctx = ctxRef.current
if (!ctx) return
const W = 360, H = 180
const barW = W / data.length
const max = Math.max(...data)
ctx.clear()
ctx.fillStyle = '#1a1a2e'
ctx.fillRect(0, 0, W, H)
data.forEach((v, i) => {
const barH = (v / max) * (H - 20)
ctx.fillStyle = `#${(100 + i * 30).toString(16)}a2f7`
ctx.fillRect(i * barW + 4, H - barH, barW - 8, barH)
})
ctx.flush()
}, [data])
return <Canvas ref={ctxRef} style={{ width: 360, height: 180 }} />
}Drawing canvas (freehand)
import { Canvas } from 'veloxkit'
import { useRef, useState } from 'react'
function DrawingCanvas() {
const ctxRef = useRef(null)
const strokes = useRef([])
const drawing = useRef(false)
const redraw = () => {
const ctx = ctxRef.current
if (!ctx) return
ctx.clear()
ctx.fillStyle = '#0D0D14'
ctx.fillRect(0, 0, 400, 300)
ctx.strokeStyle = '#7aa2f7'
ctx.lineWidth = 2
for (const stroke of strokes.current) {
for (let i = 1; i < stroke.length; i++) {
const [x0, y0] = stroke[i - 1]
const [x1, y1] = stroke[i]
ctx.strokeLine(x0, y0, x1, y1)
}
}
ctx.flush()
}
return (
<Canvas
ref={ctxRef}
style={{ width: 400, height: 300 }}
onDragStart={({ x, y }) => {
drawing.current = true
strokes.current.push([[x, y]])
}}
onDragMove={({ x, y }) => {
if (!drawing.current) return
const last = strokes.current[strokes.current.length - 1]
last.push([x, y])
redraw()
}}
onDragEnd={() => { drawing.current = false }}
/>
)
}All drawing uses Vello's 2D scene graph — GPU-accelerated via wgpu. The same pipeline renders your React component tree, so the Canvas integrates without any extra compositor step.
See also
- Canvas 2D guide — line charts, animated rings, interactive drawing
- Canvas (3D)