Yet another web 2D rendering lib.
npm install @lancercomet/zoom-panbash
npm install @lancercomet/zoom-pan
`
Quick Start
$3
`typescript
import { ViewManager, ContentLayerManager, createInteractionPlugin } from '@lancercomet/zoom-pan'
const layerManager = new ContentLayerManager()
const view = new ViewManager(canvas)
view.registerLayerManager(layerManager)
view.use(createInteractionPlugin())
// Add an image
await layerManager.createImageLayer({
src: 'image.png',
x: 0,
y: 0
})
`
$3
`typescript
import {
ViewManager,
ContentLayerManager,
CanvasLayer,
HistoryManager,
createInteractionPlugin,
createBoundsPlugin,
createSnapshotCommand
} from '@lancercomet/zoom-pan'
const layerManager = new ContentLayerManager()
const historyManager = new HistoryManager({ maxHistorySize: 50 })
const view = new ViewManager(canvas)
view.registerLayerManager(layerManager)
// Interaction plugin: controls pan/zoom
// - Touch gestures (pan/pinch) always work
// - Mouse/pen pan controlled by setPanEnabled()
const interaction = view.use(createInteractionPlugin())
// Document plugin: sets canvas bounds
const doc = view.use(createBoundsPlugin({
rect: { x: 0, y: 0, width: 1200, height: 800 },
margins: { left: 50, right: 50, top: 50, bottom: 50 }
}))
doc.zoomToFit()
// Create drawable layer
const drawLayer = new CanvasLayer({
width: 1200,
height: 800
})
layerManager.addLayer(drawLayer)
// Switch to drawing mode (disable mouse/pen pan, touch still works)
interaction.setPanEnabled(false)
// Drawing with undo support
let snapshotBefore: ImageData | null = null
canvas.onpointerdown = (e) => {
if (e.pointerType === 'touch') return // Let plugin handle touch
const { wx, wy } = view.toWorld(e.offsetX, e.offsetY)
snapshotBefore = drawLayer.captureSnapshot()
drawLayer.beginStroke(wx, wy)
}
canvas.onpointermove = (e) => {
if (e.pointerType === 'touch') return
if (e.buttons !== 1) return
const { wx, wy } = view.toWorld(e.offsetX, e.offsetY)
drawLayer.stroke(wx, wy, '#000', 10, e.pressure, 'brush')
view.requestRender()
}
canvas.onpointerup = () => {
drawLayer.endStroke()
const snapshotAfter = drawLayer.captureSnapshot()
const cmd = createSnapshotCommand(drawLayer, snapshotBefore, snapshotAfter)
if (cmd) historyManager.addCommand(cmd)
}
// Undo/Redo
document.onkeydown = (e) => {
if (e.ctrlKey && e.key === 'z') historyManager.undo()
if (e.ctrlKey && e.key === 'y') historyManager.redo()
}
`
Core Concepts
$3
The main viewport controller. Handles coordinate transformation and rendering.
`typescript
const view = new ViewManager(canvas, {
minZoom: 0.2,
maxZoom: 10,
background: '#fff'
})
// Coordinate conversion
const { wx, wy } = view.toWorld(screenX, screenY)
const { sx, sy } = view.toScreen(worldX, worldY)
// Programmatic zoom
view.zoomToAtScreen(anchorX, anchorY, 2.0)
view.zoomByFactorAtScreen(anchorX, anchorY, 1.5)
`
$3
Handles user input for pan and zoom.
`typescript
const interaction = view.use(createInteractionPlugin())
// Pan enabled affects mouse/pen only
// Touch gestures (single-finger pan, two-finger pinch) always work
interaction.setPanEnabled(true) // Mouse/pen can pan
interaction.setPanEnabled(false) // Mouse/pen cannot pan (for drawing mode)
interaction.setZoomEnabled(true) // Wheel zoom & pinch zoom enabled
`
$3
Defines document bounds and provides pan clamping.
`typescript
const doc = view.use(createBoundsPlugin({
rect: { x: 0, y: 0, width: 1200, height: 800 },
margins: { left: 50, right: 50, top: 50, bottom: 50 },
drawBorder: true,
background: '#f0f0f0',
shadow: { blur: 20, color: 'rgba(0,0,0,0.3)', offsetX: 0, offsetY: 5 }
}))
doc.zoomToFit() // Fit document to viewport
doc.setPanClampMode('minVisible') // 'margin' | 'minVisible'
`
$3
`typescript
// Content layers (world space)
const contentManager = new ContentLayerManager()
view.registerLayerManager(contentManager)
// Canvas layer for drawing
const layer = new CanvasLayer({ width: 1200, height: 800 })
contentManager.addLayer(layer)
// Image layer
const imgLayer = await contentManager.createImageLayer({ src: 'image.png' })
// Remove layer (destroys it)
contentManager.removeLayer(layer.id)
// Detach layer (keeps it for undo)
contentManager.detachLayer(layer.id)
`
$3
`typescript
const history = new HistoryManager({ maxHistorySize: 50 })
// Snapshot-based undo for drawing
const before = layer.captureSnapshot()
// ... draw ...
const after = layer.captureSnapshot()
const cmd = createSnapshotCommand(layer, before, after)
if (cmd) history.addCommand(cmd)
// Layer commands
import { CreateLayerCommand, DeleteLayerCommand } from '@lancercomet/zoom-pan'
history.undo()
history.redo()
history.canUndo()
history.canRedo()
`
Examples
See examples/ folder:
- examples/viewer/ - Simple image viewer
- examples/painter/ - Full drawing app with brush, eraser, layers, undo/redo
- examples/doc/` - Document mode demo with background and shadow