Turtles
Turtles

Reputation: 504

HTML Canvas - Zooming in and out in exactly on the mouse pointer using the mouse wheel - Check codepen example

I just want to be able to zoom IN and OUT exactly on the mouse cursor using the mouse wheel. It should work as it is implemented in the google maps!

I have made an example on codepen to highlight my problem. Please, test it to see the problem. Just go with the mouse pointer to the left eye in the image. Zoom OUT a lot, then move the mouse pointer to the right eye and zoom IN again. You will see that it is not zooming exactly where the mouse position is.

Animation with the problem:

enter link description here

The problem is that I am using the ctx.scale for scaling the image and ctx.translate to make the canvas to comeback to the original state. It is a really fast solution but it is not working so precisely. Could someone help me to solve this problem?

Goal: To be able to zoom OUT and IN in different points at the image, exactly as is beeing done by google maps using the mouse wheel.

Below it is the link of my code example to help to highlight the problem. Any solution will be very appreciate!

[https://codepen.io/MKTechStation/pen/abEyBJm?editors=1111][2]

Thank you very much in advance!

Upvotes: 2

Views: 1979

Answers (1)

Turtles
Turtles

Reputation: 504

Ok, I could find a solution. It consist basically to track all the transformations and save it as inverted matrix. So, now the really correct position of the mouse is calculated regards to the transformations. Please, check the solution here.

window.onload = function () {
  const img = document.querySelector("img");
  const canvas = document.querySelector("canvas");
  const ctx = canvas.getContext("2d");
  trackTransforms(ctx);

  let zoom = 1;
  let mousePos = {
    x: 0,
    y: 0
  };

  draw();
  document.body.addEventListener("mousemove", (e) => {
    const canvas = document.querySelector("canvas");
    const rect = canvas.getBoundingClientRect();
    mousePos.x = e.clientX - rect.left;
    mousePos.y = e.clientY - rect.top;
  });

  document.getElementById("canvas").addEventListener("wheel", (e) => {
    e.preventDefault();
    zoom = e.deltaY < 0 ? 1.1 : 0.9;
    scaleDraw();
    draw();
  });

  function draw() {
    ctx.save();
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.restore();
    ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
  }

  function scaleDraw() {
    const pt = ctx.transformedPoint(mousePos.x, mousePos.y);
    console.log("PT: " + `${pt.x}, ${pt.y}`);
    console.log("MOUSEPOINTER: " + `${mousePos.x}, ${mousePos.y}`);
    ctx.translate(pt.x, pt.y);
    ctx.scale(zoom, zoom);
    ctx.translate(-pt.x, -pt.y);
  }
};

function trackTransforms(ctx) {
  debugger;
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
  let xform = svg.createSVGMatrix();

  const savedTransforms = [];
  let save = ctx.save;
  ctx.save = () => {
    savedTransforms.push(xform.translate(0, 0));
    return save.call(ctx);
  };

  let restore = ctx.restore;
  ctx.restore = () => {
    xform = savedTransforms.pop();
    return restore.call(ctx);
  };

  let scale = ctx.scale;
  ctx.scale = (sx, sy) => {
    xform = xform.scaleNonUniform(sx, sy);
    return scale.call(ctx, sx, sy);
  };

  let translate = ctx.translate;
  ctx.translate = function (dx, dy) {
    xform = xform.translate(dx, dy);
    return translate.call(ctx, dx, dy);
  };

  let pt = svg.createSVGPoint();
  ctx.transformedPoint = (x, y) => {
    pt.x = x;
    pt.y = y;
    return pt.matrixTransform(xform.inverse());
  };
}

https://codepen.io/MKTechStation/pen/pobbKOj

I hope it can help someone. I am still open to any other better solution than that!!

solution

Upvotes: 6

Related Questions