Skip to content

Getting Started

This page builds a small interactive scene from scratch. By the end you will have shapes you can select, drag, resize, rotate, zoom, and undo — exactly like the live demo below (which is the engine, running in your browser).

Click a shape · drag the handles · scroll to zoom

If you have not added the package yet, see Installation.

The Stage is the root. It takes over a host element, creates the <canvas>, and owns the camera, renderer, and event manager.

import { Stage } from '@veyrajs/core'
const stage = new Stage({
container: document.querySelector('#app')!,
width: 800,
height: 480,
background: '#0b1220',
})

Shapes never live directly on the stage — they live on a Layer (a render partition). Add one layer and you are ready to draw.

import { Layer } from '@veyrajs/core'
const layer = new Layer()
stage.add(layer)

Shapes are real classes with typed config. Position them with the node transform (x, y), and paint them with fill / stroke.

import { Rect, Circle } from '@veyrajs/core'
const rect = new Rect({ x: 40, y: 40, width: 150, height: 90, fill: '#38bdf8' })
layer.add(rect)
layer.add(new Circle({ x: 300, y: 100, radius: 50, fill: '#f472b6' }))

You never call render(). Mutating a node (rect.x = 60) marks the scene dirty and schedules one coalesced redraw on the next animation frame.

Events are federated: they hit-test the scene and dispatch with DOM-style capture → target → bubble. Bind a handler with .on(...).

rect.on('click', () => {
rect.x += 20 // moves on click; the redraw is automatic
})

The camera converts between screen and world space. Zoom about a point, or pan by a delta.

import { Vec2 } from '@veyrajs/core'
stage.camera.zoomAt(new Vec2(300, 100), 1.2) // 1.2× about (300, 100)
stage.on('wheel', (e) => {
e.preventDefault()
stage.camera.zoomAt(e.screenPoint, e.deltaY < 0 ? 1.1 : 1 / 1.1)
})

SelectionController wires click-to-select, the bounds box, resize + rotate handles, and cursor feedback — and every drag becomes an undoable command. Give it a History and you have undo/redo for free.

import { History, SelectionController } from '@veyrajs/core'
const history = new History()
new SelectionController(stage, { history })
// later, from a toolbar button:
history.undo()
history.redo()
import {
Stage, Layer, Rect, Circle, Vec2, History, SelectionController,
} from '@veyrajs/core'
const stage = new Stage({
container: document.querySelector('#app')!,
width: 800,
height: 480,
background: '#0b1220',
})
const layer = new Layer()
stage.add(layer)
const rect = new Rect({ x: 40, y: 40, width: 150, height: 90, fill: '#38bdf8' })
layer.add(rect)
layer.add(new Circle({ x: 300, y: 100, radius: 50, fill: '#f472b6' }))
rect.on('click', () => { rect.x += 20 })
stage.camera.zoomAt(new Vec2(300, 100), 1.2)
const history = new History()
new SelectionController(stage, { history })

That is the entire demo at the top of this page.

The same scene, declaratively. Props drive the node, events come back as callbacks, and a ref / bind:node / public field hands you the underlying engine node when you need it.

// React — @veyrajs/react
import { ACStage, ACLayer, ACRect, ACCircle } from '@veyrajs/react'
export function Scene() {
return (
<ACStage width={800} height={480} background="#0b1220" selectable>
<ACLayer>
<ACRect x={40} y={40} width={150} height={90} fill="#38bdf8" />
<ACCircle x={300} y={100} radius={50} fill="#f472b6" />
</ACLayer>
</ACStage>
)
}

The selectable prop wires the SelectionController + History for you (click-select, transform handles, undo/redo). The very same markup works in Vue, Svelte, and Angular — see Framework Adapters.