I have a system to scale, rotate, and translate an image on a canvas. Everything is working as expected, except the translation of the image after scaling.
When the scale is not equal to 1, then the dragging starts to drift (see example illustration below):
The illustration above shows where I start dragging the image, and where my mouse position ends up.
Is there a way of maintaining the mouse position, so that when I'm dragging the image, the mouse stays in the same location of the image?
How can I achieve this, whilst still keeping the rest of the scaling and rotation (from the canvas centre) functionality?
let rotation = 0;
let scale = 1;
let x = 0;
let y = 0;
let startX = 0;
let startY = 0;
let lastX = 0;
let lastY = 0;
let pointerDown = false;
const canvas = document.querySelector("#canvas");
const ctx = canvas.getContext("2d");
const imgWidth = 480;
const imgHeight = 300;
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resizeCanvas();
window.addEventListener("resize", resizeCanvas);
const img = new Image();
img.crossOrigin = "anonymous";
img.src = "https://i.imgur.com/3q3kNGh.png";
function onPointerDown(event) {
pointerDown = true;
startX = (event.clientX - canvas.offsetLeft) / imgWidth;
startY = (event.clientY - canvas.offsetTop) / imgHeight;
}
function onPointerMove(event) {
if (!pointerDown) return;
let deltaX = (event.clientX - canvas.offsetLeft) / imgWidth - startX;
let deltaY = (event.clientY - canvas.offsetTop) / imgHeight - startY;
const { x: dX, y: dY } = rotate(
deltaX * imgWidth,
deltaY * imgHeight,
rotation
);
x = lastX dX / imgWidth;
y = lastY dY / imgHeight;
}
function onPointerUp() {
pointerDown = false;
lastX = x;
lastY = y;
}
window.addEventListener("pointerdown", onPointerDown);
window.addEventListener("pointermove", onPointerMove);
window.addEventListener("pointerup", onPointerUp);
window.addEventListener("keydown", (event) => {
const key = event.key.toLowerCase();
switch (key) {
case "r":
rotation = (rotation 5) % 360;
break;
case "-":
scale = Math.max(0, scale - 0.1);
break;
case "=":
scale = Math.min(2, scale 0.1);
break;
default:
break;
}
});
function rotate(x, y, rotation) {
const panXX = x * Math.cos((rotation * Math.PI) / 180);
const panXY = y * Math.sin((rotation * Math.PI) / 180);
const panYY = y * Math.cos((rotation * Math.PI) / 180);
const panYX = x * Math.sin((rotation * Math.PI) / 180);
const panX = panXX panXY;
const panY = panYY - panYX;
return { x: panX, y: panY };
}
(function draw() {
requestAnimationFrame(draw);
const imgX = imgWidth * x;
const imgY = imgHeight * y;
const ox = canvas.width / 2 - imgX;
const oy = canvas.height / 2 - imgY;
const { x: rx, y: ry } = rotate(imgX, imgY, rotation);
const matrix = new DOMMatrix()
.translate(ox, oy)
.rotate(rotation)
.translate(-ox, -oy)
.translate(rx, ry)
.translate(ox, oy)
.scale(scale)
.translate(-ox, -oy);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.setTransform(matrix);
ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
ctx.resetTransform();
ctx.fillStyle = "rgba(255, 0, 0, 0.5)";
ctx.fillRect(canvas.width / 2 - 5, canvas.height / 2 - 5, 10, 10);
})();
html,
body {
margin: 0;
padding: 0;
}
canvas {
display: block;
}
pre {
position: absolute;
bottom: 0;
left: 0;
padding: 0.5em;
pointer-events: none;
user-select: none;
}
<canvas id="canvas"></canvas>
<pre>
Hotkeys
---
Rotate: r
Zoom out: -
Zoom in: =
</pre>
CodePudding user response:
You need to divide mouse movement by the current scale
x = lastX dX / imgWidth / scale;
y = lastY dY / imgHeight / scale;

