Animation
Veyrajs deliberately ships no tween or animation engine. The scene graph is mutable, so you
animate the same way the browser does: mutate node properties inside a requestAnimationFrame
loop and call stage.requestRender(). The engine coalesces the redraw to one frame.
Bouncing
Section titled “Bouncing”import { Circle } from '@veyrajs/core'import { button, createStage, cycle, disposeStage, toolbar } from './_kit'
// Veyrajs ships no tween engine on purpose — you animate by mutating node properties inside a// requestAnimationFrame loop and calling stage.requestRender(). Here: balls with velocity that// bounce off the walls.export function init(host: HTMLElement): () => void { const stage = createStage(host) const layer = stage.createLayer() const R = 16 const balls = Array.from({ length: 26 }, (_, i) => { const node = new Circle({ x: R + Math.random() * (stage.width - 2 * R), y: R + Math.random() * (stage.height - 2 * R), radius: R, fill: cycle[i % cycle.length], }) layer.add(node) const angle = Math.random() * Math.PI * 2 const speed = 1.5 + Math.random() * 2 return { node, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed } })
let running = true let raf = 0 const tick = (): void => { const w = stage.width const h = stage.height for (const b of balls) { b.node.x += b.vx b.node.y += b.vy if (b.node.x < R) { b.node.x = R b.vx = Math.abs(b.vx) } else if (b.node.x > w - R) { b.node.x = w - R b.vx = -Math.abs(b.vx) } if (b.node.y < R) { b.node.y = R b.vy = Math.abs(b.vy) } else if (b.node.y > h - R) { b.node.y = h - R b.vy = -Math.abs(b.vy) } } stage.requestRender() if (running) raf = requestAnimationFrame(tick) } raf = requestAnimationFrame(tick)
const bar = toolbar(host) const toggle = button('Pause', () => { running = !running toggle.textContent = running ? 'Pause' : 'Play' if (running) raf = requestAnimationFrame(tick) }) bar.append(toggle) const hint = document.createElement('span') hint.className = 'veyrajs-demo__hint' hint.textContent = 'No tween engine — just rAF + requestRender()' bar.append(hint)
return () => { running = false cancelAnimationFrame(raf) disposeStage(stage) }}Orbiting
Section titled “Orbiting”Each body’s position is recomputed every frame from an orbit angle:
import { Circle } from '@veyrajs/core'import { createStage, disposeStage, palette, toolbar } from './_kit'
// Angle-driven motion: each planet's position is recomputed every frame from a growing time// value and its orbit radius/rate. The small indigo moon orbits the second planet — its center// is just the planet's position plus its own offset.export function init(host: HTMLElement): () => void { const stage = createStage(host) const layer = stage.createLayer() const sun = new Circle({ radius: 22, fill: palette.amber }) const planets = [ { orbit: 72, rate: 1.7, node: new Circle({ radius: 10, fill: palette.blue }) }, { orbit: 124, rate: 1.05, node: new Circle({ radius: 15, fill: palette.cyan }) }, { orbit: 186, rate: 0.62, node: new Circle({ radius: 9, fill: palette.teal }) }, ] const moon = new Circle({ radius: 5, fill: palette.indigo }) layer.add(sun, ...planets.map((p) => p.node), moon)
let t = 0 let raf = 0 const tick = (): void => { t += 0.016 const cx = stage.width / 2 const cy = stage.height / 2 sun.x = cx sun.y = cy for (const p of planets) { p.node.x = cx + Math.cos(t * p.rate) * p.orbit p.node.y = cy + Math.sin(t * p.rate) * p.orbit } const earth = planets[1].node moon.x = earth.x + Math.cos(t * 4) * 30 moon.y = earth.y + Math.sin(t * 4) * 30 stage.requestRender() raf = requestAnimationFrame(tick) } raf = requestAnimationFrame(tick)
const bar = toolbar(host) const hint = document.createElement('span') hint.className = 'veyrajs-demo__hint' hint.textContent = 'Positions recomputed each frame from orbit angles' bar.append(hint)
return () => { cancelAnimationFrame(raf) disposeStage(stage) }}Spring follow
Section titled “Spring follow”A tiny spring integrator makes the dot chase the pointer:
import { Circle, Line } from '@veyrajs/core'import { createStage, disposeStage, palette, toolbar } from './_kit'
// The blue dot chases the pointer with a simple spring — each frame, acceleration pulls it toward// the target, velocity is damped, and the position integrates. A trailing line shows the lag.export function init(host: HTMLElement): () => void { const stage = createStage(host) const layer = stage.createLayer() const target = { x: 0, y: 0 } const pos = { x: 0, y: 0 } const vel = { x: 0, y: 0 } let primed = false
const link = new Line({ points: [ { x: 0, y: 0 }, { x: 0, y: 0 }, ], stroke: palette.slate, strokeWidth: 2, }) const anchor = new Circle({ radius: 5, fill: palette.amber }) const dot = new Circle({ radius: 16, fill: palette.blue }) layer.add(link, anchor, dot)
stage.on('pointermove', (e) => { target.x = e.worldPoint.x target.y = e.worldPoint.y if (!primed) { primed = true pos.x = target.x pos.y = target.y } })
const stiffness = 0.08 const damping = 0.78 let raf = 0 const tick = (): void => { vel.x = (vel.x + (target.x - pos.x) * stiffness) * damping vel.y = (vel.y + (target.y - pos.y) * stiffness) * damping pos.x += vel.x pos.y += vel.y dot.x = pos.x dot.y = pos.y anchor.x = target.x anchor.y = target.y link.points = [ { x: target.x, y: target.y }, { x: pos.x, y: pos.y }, ] stage.requestRender() raf = requestAnimationFrame(tick) } raf = requestAnimationFrame(tick)
const bar = toolbar(host) const hint = document.createElement('span') hint.className = 'veyrajs-demo__hint' hint.textContent = 'Move the pointer — the dot springs toward it' bar.append(hint)
return () => { cancelAnimationFrame(raf) disposeStage(stage) }}