Translating and scaling for better zoom

In this project I’m working on, I’ve implemented panning and zooming around. Panning works great, and the zoom works, but it’s unnatural, as it zooms in on the origin instead of where your mouse cursor is.

I’ve created a basic version here which basically is what I have right now. I would like it so that you can zoom in at the mouse pointer, not at the origin instead. I’ve searched through this forum and have found several posts on this topic, (including this, this, and this) and I have tried to implement them with no success.

I know I need to first translate to the camera. (for panning) Afterward, I’m not sure exactly what to do, but I know I need to translate, then scale, and probably translate back.

What I have is:

translate(camera);  // 2d vector
scale(zoom);  // float, higher number is more zoomed in

I’ve tried many things, but this is the closest I got:

translate(camera);

translate(mouseX, mouseY);
scale(zoom);
translate(-mouseX, -mouseY);

I would really appreciate it if someone could help me with this!

Have you tried to scale first and then translate??

1 Like

Unfortunately, this does not work. :frowning: I’ve also tried multiplying the camera vector by zoom after scaling and it doesn’t fix it.

The included p5.js example in this StackOverflow post helped me solve it!

Turns out that I needed to modify my zooming in / out code, not the code that scales every frame. Here is what I have so far: (it works but it jerks a bit when I constrain it)

(inside mouse wheel event)

    let scale_factor;
    if (event.deltaY > 0) {
      scale_factor = 1 + zoom_diff;
    } else {
      scale_factor = 1 - zoom_diff;
    }

    // https://stackoverflow.com/a/70660569/10291933
    const previous = zoom;
    zoom *= scale_factor;
    zoom = Math.min(Math.max(zoom, zoom_min), zoom_max);

    if (zoom != previous) {
      camera.x = mouseX - (mouseX * scale_factor) + (camera.x * scale_factor);
      camera.y = mouseY - (mouseY * scale_factor) + (camera.y * scale_factor);
    }
    
    return false;