Skip to content

Canvas Rendering

Flexium provides declarative JSX-based canvas rendering with built-in Signal reactivity.

Why Canvas?

Canvas is essential for:

  • Data visualization (charts, graphs)
  • Game development
  • Image manipulation
  • Custom UI elements
  • Real-time animations

Traditional canvas code is imperative and hard to maintain. Flexium makes it declarative and reactive.

Basic Usage

tsx
import { Canvas, DrawRect, DrawCircle } from 'flexium/canvas'

<Canvas width={400} height={400}>
  <DrawRect x={50} y={50} width={100} height={100} fill="blue" />
  <DrawCircle x={200} y={200} radius={50} fill="red" />
</Canvas>

Reactive Canvas

Canvas elements update automatically when state changes:

tsx
import { state, effect } from 'flexium/core'
import { Canvas, DrawCircle } from 'flexium/canvas'

function AnimatedCircle() {
  const [x, setX] = state(50)

  // Animate
  effect(() => {
    const interval = setInterval(() => {
      setX(prev => (prev + 1) % 400)
    }, 16)
    return () => clearInterval(interval)
  })

  return (
    <Canvas width={400} height={400}>
      <DrawCircle x={x} y={200} radius={30} fill="blue" />
    </Canvas>
  )
}

The canvas re-renders automatically when x changes. No manual requestAnimationFrame needed!

Available Primitives

DrawRect

Draw rectangles:

tsx
<DrawRect
  x={50}
  y={50}
  width={100}
  height={100}
  fill="blue"
  stroke="black"
  strokeWidth={2}
  opacity={0.8}
/>

DrawCircle

Draw circles:

tsx
<DrawCircle
  x={100}
  y={100}
  radius={50}
  fill="red"
  stroke="black"
  strokeWidth={2}
/>

DrawPath

Draw SVG paths:

tsx
<DrawPath
  d="M 10 10 L 100 100 L 10 100 Z"
  fill="green"
  stroke="black"
  strokeWidth={2}
/>

DrawText

Render text:

tsx
<DrawText
  x={100}
  y={100}
  text="Hello Canvas!"
  fill="black"
  fontSize={20}
  fontFamily="Arial"
  fontWeight="bold"
  textAlign="center"
/>

DrawLine

Draw lines:

tsx
<DrawLine
  x1={10}
  y1={10}
  x2={100}
  y2={100}
  stroke="black"
  strokeWidth={2}
/>

DrawArc

Draw arcs:

tsx
<DrawArc
  x={100}
  y={100}
  radius={50}
  startAngle={0}
  endAngle={Math.PI}
  fill="orange"
  stroke="black"
  strokeWidth={2}
/>

Interactive Canvas

Combine with event handlers for interactivity:

tsx
import { state } from 'flexium/core'
import { Canvas, DrawCircle } from 'flexium/canvas'

function InteractiveCanvas() {
  const [mouseX, setMouseX] = state(200)
  const [mouseY, setMouseY] = state(200)

  return (
    <div
      onmousemove={(e) => {
        const rect = e.currentTarget.getBoundingClientRect()
        setMouseX(e.clientX - rect.left)
        setMouseY(e.clientY - rect.top)
      }}
    >
      <Canvas width={400} height={400}>
        <DrawCircle x={mouseX} y={mouseY} radius={20} fill="blue" />
      </Canvas>
    </div>
  )
}

Performance

Automatic Synchronization

Canvas updates are automatically synchronized and debounced with requestAnimationFrame:

tsx
const [x, setX] = state(0)
const [y, setY] = state(0)

// Both changes trigger only ONE canvas redraw
setX(100)
setY(200)

Manual Control

For advanced use cases, access the underlying canvas:

tsx
<Canvas
  width={400}
  height={400}
  ref={(canvas) => {
    if (canvas) {
      const ctx = canvas.getContext('2d')
      // Manual drawing here
    }
  }}
>
  {/* JSX primitives */}
</Canvas>

Example: Real-Time Chart

tsx
import { state, effect } from 'flexium/core'
import { Canvas, DrawLine, DrawCircle, DrawText } from 'flexium/canvas'

function RealtimeChart() {
  const [dataPoints, setDataPoints] = state([50, 60, 55, 70, 65, 80])

  // Simulate real-time data
  effect(() => {
    const interval = setInterval(() => {
      const newPoint = 50 + Math.random() * 50
      setDataPoints(prev => [...prev.slice(1), newPoint])
    }, 1000)

    return () => clearInterval(interval)
  })

  return (
    <Canvas width={600} height={300} style={{ border: '1px solid #ccc' }}>
      {/* Grid lines */}
      {[0, 1, 2, 3, 4].map((i) => (
        <DrawLine
          key={i}
          x1={0}
          y1={i * 60}
          x2={600}
          y2={i * 60}
          stroke="#eee"
          strokeWidth={1}
        />
      ))}

      {/* Data line */}
      {dataPoints().map((value, i) => {
        if (i === 0) return null
        const x1 = (i - 1) * 100
        const y1 = 300 - dataPoints()[i - 1] * 2
        const x2 = i * 100
        const y2 = 300 - value * 2

        return (
          <DrawLine
            key={i}
            x1={x1}
            y1={y1}
            x2={x2}
            y2={y2}
            stroke="blue"
            strokeWidth={2}
          />
        )
      })}

      {/* Data points */}
      {dataPoints().map((value, i) => (
        <DrawCircle
          key={i}
          x={i * 100}
          y={300 - value * 2}
          radius={4}
          fill="blue"
        />
      ))}

      {/* Labels */}
      <DrawText
        x={10}
        y={20}
        text="Real-Time Data"
        fontSize={16}
        fontWeight="bold"
        fill="black"
      />
    </Canvas>
  )
}

Next Steps

Released under the MIT License.