'How to implement Zoom and Pan in HTML Canvas Painting app

I am trying to create a painting app using HTML canvas. I want to implement zoom and panning functionality to the canvas. The user should be able to paint on the canvas, then zoom in or out(which scales the already drawn elements) and pan(which translates the elements) and then draw over them. Currently, I am stuck at implementing zoom properly.

Solutions explored:

  1. ctx.scale will scale the canvas but not the already painted elements. New elements are drawn with a scaled effect and away from the cursor.
  2. Storing the canvas in another temporary canvas. Then redrawing the original canvas with scaled temporary canvas. This results in pixelation when zoomed in and so is not desired.
tempCtx.drawImage(canvas, 0, 0);
ctx.drawImage(tempCanvas, 0, 0, cw, ch, 0, 0,  cw *scaleFactor, ch * scaleFactor);

CODE:

window.addEventListener('load', () => {
  const canvas = document.querySelector("#canvas");
  const ctx = canvas.getContext("2d");

  let penColor = "#2525c5";
  let brushSize = 4;

  const zoomInBtn = document.querySelector("#zoom-in");
  const zoomOutBtn = document.querySelector("#zoom-out");
  let scaleFactor = 1;

  zoomInBtn.addEventListener("click", () => {
    scaleFactor = scaleFactor * 1.1;
    ctx.scale(scaleFactor, scaleFactor);
  });

  zoomOutBtn.addEventListener("click", () => {
    scaleFactor = scaleFactor * 0.9;
    ctx.scale(scaleFactor, scaleFactor);
  });

  let painting = false;

  function startPosition(e) {
    painting = true;
    draw(e);
  }

  function finishedPosition() {
    painting = false;
    ctx.beginPath();
  }

  function draw(e) {
    if (!painting) return;
    ctx.lineWidth = brushSize;
    ctx.strokeStyle = penColor;
    ctx.lineCap = "round";

    let rect = e.target.getBoundingClientRect();
    let x = e.clientX - rect.left;
    let y = e.clientY - rect.top;

    ctx.lineTo(x, y);
    ctx.stroke();
    ctx.beginPath();
    ctx.moveTo(x, y);

  }

  canvas.addEventListener("mousedown", startPosition);
  canvas.addEventListener("mouseup", finishedPosition);
  canvas.addEventListener("mousemove", draw);
});
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

#toolbar {
  display: flex;
}

.tool-group {
  display: flex;
  margin-right: 40px;
}

#canvas-container {
  box-sizing: border-box;
  margin-top: 50px;
  margin-left: 50px;
  width: 500px;
  height: 500px;
  border: 2px solid black;
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  <link rel="stylesheet" href="style.css" />
  <title>Drawing App</title>
</head>

<body>
  <div id="app">
    <div id="toolbar">
      <div class="tool-group">
        <button id="zoom-in">Zoom in</button>
        <button id="zoom-out">Zoom out</button>
      </div>
    </div>
    <div id="canvas-container">
      <canvas id="canvas" width="500" height="500"></canvas>
    </div>

  </div>

  <script src="canvas2.js"></script>
</body>

</html>


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source