Pan & Zoom
Two small handlers turn a stage into an infinite, pannable canvas: drag to pan with the
camera’s panBy, and scroll to zoom with zoomAt. The camera is a view onto the world, so
panning and zooming never touch your node coordinates.
import { Stage, Rect, Circle } from '@veyrajs/core'
const stage = new Stage({ container: document.getElementById('app')!, width: 800, height: 480, background: '#0b1220',})const layer = stage.createLayer()layer.add( new Rect({ x: 120, y: 120, width: 160, height: 100, fill: '#38bdf8' }), new Circle({ x: 480, y: 240, radius: 60, fill: '#f472b6' }),)
// --- Drag to pan: feed the screen delta between moves to panBy ---let panning = falselet last = { x: 0, y: 0 }
stage.on('pointerdown', (e) => { if (e.button !== 0) return // primary button only panning = true last = e.screenPoint})
stage.on('pointermove', (e) => { if (!panning) return stage.camera.panBy(e.screenPoint.x - last.x, e.screenPoint.y - last.y) last = e.screenPoint})
stage.on('pointerup', () => { panning = false})
// --- Wheel to zoom about the cursor ---stage.on('wheel', (e) => { e.preventDefault() // stop the page from scrolling const factor = e.deltaY < 0 ? 1.1 : 1 / 1.1 stage.camera.zoomAt(e.screenPoint, factor)})Listen on the stage so handlers fire even when the pointer hits empty canvas (the stage
becomes the event target). panBy takes a delta in screen pixels, so we track the last
pointer position and pass the difference each move. zoomAt(anchor, factor) is
cursor-anchored: the world point under e.screenPoint stays put while everything scales
around it, and e.preventDefault() keeps the wheel from scrolling the page (the wheel
listener is non-passive). Zoom is clamped to [minZoom, maxZoom] automatically.