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;
}

JavaScript

class InteractionManager {
  constructor(canvas, context, shapes) {
    this.canvas = canvas;
    this.ctx = context;
    this.shapes = shapes;
    this.plugins = { shape: [], container: [] };

    this.state = {
      draggingShape: null,
      draggingCanvas: false,
      scale: 1,
      offsetX: 0,
      offsetY: 0,
      lastX: 0,
      lastY: 0,
    };

    this._bindEvents();
  }

  // Convert screen to world coordinates
  toWorld(x, y) {
    return {
      x: (x - this.state.offsetX) / this.state.scale,
      y: (y - this.state.offsetY) / this.state.scale,
    };
  }

  _bindEvents() {
    const getMouse = (e) => ({
      x: e.offsetX,
      y: e.offsetY
    });

    this.canvas.addEventListener("mousedown", (e) => {
      const { x, y } = getMouse(e);
      const world = this.toWorld(x, y);
      this.state.lastX = x;
      this.state.lastY = y;

      const target = this.getTarget(world.x, world.y);

      if (e.button === 0) {
        if (target) {
          this.state.draggingShape = target;
          this._dispatch("shape", "dragstart", e, target);
        } else {
          this.state.draggingCanvas = true;
          this._dispatch("container", "dragstart", e, null);
        }
      }
    });

    this.canvas.addEventListener("mousemove", (e) => {
      const { x, y } = getMouse(e);
      const dx = x - this.state.lastX;
      const dy = y - this.state.lastY;

      if (this.state.draggingShape) {
        this.state.draggingShape.x += dx / this.state.scale;
        this.state.draggingShape.y += dy / this.state.scale;
        this._dispatch("shape", "drag", e, this.state.draggingShape);
      } else if (this.state.draggingCanvas) {
        this.state.offsetX += dx;
        this.state.offsetY += dy;
        this._dispatch("container", "drag", e, null);
      }

      this.state.lastX = x;
      this.state.lastY = y;

      requestAnimationFrame(draw);
    });

    this.canvas.addEventListener("mouseup", (e) => {
      if (this.state.draggingShape) {
  ...