Allow canvas shapes to be dragged and zoomed
by Rajesh Danabal
HTML
<canvas id="canvas" width="800" height="600"></canvas>
CSS
body {
margin: 0;
overflow: hidden;
}
canvas {
border: 1px solid #ccc;
display: block;
}
TypeScript
type Shape = {
id: string;
x: number;
y: number;
width: number;
height: number;
color: string;
};
type InteractionEventHandlers = {
onShapeClick?: (shape: Shape, e: MouseEvent) => void;
onShapeContextMenu?: (shape: Shape, e: MouseEvent) => void;
onShapeDragStart?: (shape: Shape, e: MouseEvent) => void;
onShapeDrag?: (shape: Shape, e: MouseEvent) => void;
onShapeDragEnd?: (shape: Shape, e: MouseEvent) => void;
onCanvasClick?: (e: MouseEvent) => void;
onCanvasContextMenu?: (e: MouseEvent) => void;
onCanvasDragStart?: (e: MouseEvent) => void;
onCanvasDrag?: (e: MouseEvent) => void;
onCanvasDragEnd?: (e: MouseEvent) => void;
onCanvasZoom?: (e: WheelEvent, scale: number) => void;
};
type InteractionState = {
draggingShape: Shape | null;
draggingCanvas: boolean;
scale: number;
offsetX: number;
offsetY: number;
lastX: number;
lastY: number;
};
class InteractionManager {
private canvas: HTMLCanvasElement;
private context: CanvasRenderingContext2D;
private shapes: Shape[];
public state: InteractionState;
private handlers: InteractionEventHandlers;
constructor(
canvas: HTMLCanvasElement,
context: CanvasRenderingContext2D,
shapes: Shape[],
handlers: InteractionEventHandlers = {}
) {
this.canvas = canvas;
this.context = context;
this.shapes = shapes;
this.handlers = handlers;
this.state = {
draggingShape: null,
draggingCanvas: false,
scale: 1,
offsetX: 0,
offsetY: 0,
lastX: 0,
lastY: 0,
};
this.bindEvents();
}
private toWorld(x: number, y: number) {
return {
x: (x - this.state.offsetX) / this.state.scale,
y: (y - this.state.offsetY) / this.state.scale,
};
}
private getShapeAt(x: number, y: number): Shape | null {
for (let i = this.shapes.length - 1; i >= 0; i--) {
const s = this.shapes[i];
if (x >= s.x && x <= s.x + s.width && y >= s.y && y <= s.y + s.height) {
return...