How would I go about implementing a moving origin on zoom on a canvas?

Viewed 34

I'm having some issues with implementing a custom zoom.

I have most of the zoom functionality working, however, it zooms "around" the origin, in this case, the top-left corner of the grid. Instead, I want it to zoom in around the cursor.

The canvas is as big as the window (I'll be adding UI on top of the canvas, kinda like photoshop), and I'm drawing a grid to the canvas that is WIDTH x HEIGHT pixels large, "scaled up" by a factor of PIXEL_SIZE.

The important part of this, this event handler right here

const WIDTH = 32;
const HEIGHT = 24;
const PIXEL_SIZE = 10;
...
// controls how big the grid is
let scale = 1.5;

// controls where the grid is drawn, 
// starts exactly in the middle of the screen
let originX = canvas.width / 2 - (WIDTH * PIXEL_SIZE * scale) / 2;
let originY = canvas.height / 2 - (HEIGHT * PIXEL_SIZE * scale) / 2;

function zoom(event) {
  event.preventDefault();

  const wheel = event.deltaY < 0 ? 1 : -1;
  const z = Math.exp(wheel * 0.05);

  scale *= z;

  // this adjusts the scale just fine-ish
  // however, the origin needs to move in relation
  // to the cursor position
}

window.addEventListener("wheel", zoom, { passive: false });

I've tried a few things, but for some reason, my brain can't do the math at the moment. I've tried using ctx.translate and ctx.scale, but neither of them seems to do what I want to do (though I could be using them wrong).

Here's a JSFiddle with what I've got so far. If someone could take a look, that would be awesome.

1 Answers

Pretty cool code. Zoooming in on a specific point can be tricky and could be done a number of ways. The main problem is where exactly do you want to zoom and at what scale/how fast when nearing the max zoom.

I would suggest starting off with defining an offset

...
// starts exactly in the middle of the screen
let originX = canvas.width / 2 - (WIDTH * PIXEL_SIZE * scale) / 2;
let originY = canvas.height / 2 - (HEIGHT * PIXEL_SIZE * scale) / 2;

let offsetX = originX;
let offsetY = originY;
...

And then computing the offset using event.x and event.y of the MouseEvent in the zoom callback, similar to:

  function zoom(event) {
      ...

      // event.x and event.y returns the position relative to ...something
      // To properly scale the values, you could get the relative position
      // and size of the canvas and find your offset, for example
      // (viwportsizeX - canvasSizeX) + event.X / (scaleX) and the same for Y 
      // when the position is realtive to the viewport
      offsetX = event.x;
      offsetY = event.y;
  }

And of course update the offset in the animation mainloop:

function animate() {
    requestAnimationFrame(animate);
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    bufferCtx.putImageData(data, 0, 0);

    ctx.drawImage(
        bufferCanvas,
        originX - offsetX,
        originY - offsetY,
        WIDTH * PIXEL_SIZE * scale,
        HEIGHT * PIXEL_SIZE * scale
    );
 }

Zooming in exactly on the point of the cursor is possible by computing the offsets relative to the canvas element being zoomed on.

JSFiddle

Related