🚧 VeloxKit is pre-release software. APIs may change before v1.0. Get started →
Documentation
Components
Canvas (2D)

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

PropTypeDescription
refReact.Ref<CanvasContext>Attach to access the drawing context
styleViewStyleWidth 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

PropertyTypeDefaultDescription
ctx.fillStylestring | [r,g,b,a][255,255,255,255]Fill color for shapes and text
ctx.strokeStylestring | [r,g,b,a][255,255,255,255]Stroke color
ctx.lineWidthnumber1Stroke line width in pixels

Colors accept CSS hex strings ('#ff0000', '#f00') or [r, g, b, a] arrays (0–255).

Methods

MethodDescription
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