Declarative canvas rendering for Svelte 4.
Svelte Canvas Layers wraps the native HTML <canvas> element with a small layer-based rendering runtime. It gives Svelte apps a component model for defining canvas layers, handling pointer events, performing hit testing, and rendering either on the main thread or inside a Web Worker.
Svelte Canvas Layers currently targets Svelte 4 only.
The repository includes a small whiteboard-style demo app built on top of the canvas layer API. It shows camera controls, editable objects, selection, arrows, and layer picking.
It is a playground for the kind of architecture behind whiteboards, drawing tools, visual editors, diagrams, and canvas-based UI: a scene made of interactive layers, a camera that transforms user input into world coordinates, and UI workflows built on top of those primitives.
The project began as an exploration of the Canvas 2D API and Svelte. The browser canvas API is powerful, but it is imperative by default. Svelte Canvas Layers keeps the canvas itself native while giving Svelte apps a component model for organizing rendering and interaction.
- Svelte wrapper around HTML
<canvas> - Declarative
<Canvas>and<Layer>components - Direct Canvas 2D rendering through
CanvasRenderingContext2D - Layer-level pointer, click, hover, and drag-style interactions
- Color-based hit testing for interactive canvas objects
- Main-thread rendering support
- Web Worker rendering support
- Worker-owned renderer registry
- Built for Svelte 4
<script lang="ts">
import { Canvas, Layer } from '@canvas/engine'
const box = { x: 40, y: 40, width: 120, height: 80, color: 'tomato' }
</script>
<Canvas width={800} height={600} useLayerEvents>
<Layer
bounds={{
x0: box.x,
y0: box.y,
x1: box.x + box.width,
y1: box.y + box.height,
}}
render={({ ctx }) => {
ctx.fillStyle = box.color
ctx.fillRect(box.x, box.y, box.width, box.height)
}}
on:click={() => console.log('box clicked')}
/>
</Canvas><script lang="ts">
import { WorkerCanvas, WorkerLayer } from '@canvas/engine'
const box = { x: 40, y: 40, width: 120, height: 80, color: 'tomato' }
</script>
<WorkerCanvas
createWorker={() =>
new Worker(new URL('./canvas.worker.ts', import.meta.url), { type: 'module' })
}
>
<WorkerLayer renderer="box" data={box} />
</WorkerCanvas>// canvas.worker.ts
import {
exposeCanvasWorker,
type WorkerRenderRegistry,
} from '@canvas/engine/worker-runtime'
const renderers = {
box({ ctx, data }) {
ctx.fillStyle = data.color
ctx.fillRect(data.x, data.y, data.width, data.height)
},
} satisfies WorkerRenderRegistry
exposeCanvasWorker(renderers)Storybook contains focused examples for the engine package, including main-thread rendering, worker rendering, color picking, layer events, pixel ratio handling, and resizable layers.
pnpm dev:storybookpnpm install
pnpm dev
pnpm checkUseful package checks:
pnpm --filter @canvas/engine check
pnpm --filter @canvas/engine build
pnpm --filter @canvas/storybook buildThe app and Storybook build as separate static targets:
pnpm build:app
pnpm build:storybook
pnpm build:deployOutputs:
- App:
dist/app - Storybook:
packages/storybook/dist
Preview locally:
pnpm preview:app # app on http://localhost:4173
pnpm preview:storybook # storybook on http://localhost:6007