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;