I\'m trying to create zoom effect on canvas, and I\'ve managed to do that, but there\'s a small problem. Zooming (scaling) origin is top left of the canvas. How can I specify a
If it is only zoom and pan then the solution is simple.
You need to track two origins. One is the position of the mouse in the world coordinates (box and circle position) and the other is the position of the mouse in the screen coordinates (canvas pixels)
You will need to get used to converting from one coordinate system to the other. That is done via the inverse function. From world coords to screen coords can be reversed with the inverse function that will convert from screen coords to world coords.
Examples of inverting some simple functions
2 * 10 = 20
the inverse is 20 / 10 = 2
2 + 3 = 5
the inverse is 5 - 3 = 2
(3 - 1) * 5 = 10
the inverse is 10 * (1/5) + 1 = 3
Multiply becomes * 1 over. ie x*5
becomes x * 1/5
(or just x/5
)
Adds become subtracts and subtract become add and what is first become last and last becomes first (3 - first) * last = result
the inverse is result / last + first = 3
So you zoom a coordinate (world coord position of box) and get the screen position of box in pixels. If you want the world coords of a screen pixel you apply the inverse
It's all mouthful so here is your code doing what you need with some comments and I added mousemove, button stuff and other stuff because you need the mouse pos and no point in zooming if you can not pan, need to stop mouse button locking and stop wheel scrolling blah blah... To pan just move the world origin (in code) click drag in UI. Also I am lazy and got rid of the global.zoom.origin.x
stuff now scale
is well you know, and wx,wy
,sx,sy
are origins read code for what is what.
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
canvas.width = 600;
canvas.height = 400;
// lazy programmers globals
var scale = 1;
var wx = 0; // world zoom origin
var wy = 0;
var sx = 0; // mouse screen pos
var sy = 0;
var mouse = {};
mouse.x = 0; // pixel pos of mouse
mouse.y = 0;
mouse.rx = 0; // mouse real (world) pos
mouse.ry = 0;
mouse.button = 0;
function zoomed(number) { // just scale
return Math.floor(number * scale);
}
// converts from world coord to screen pixel coord
function zoomedX(number) { // scale & origin X
return Math.floor((number - wx) * scale + sx);
}
function zoomedY(number) { // scale & origin Y
return Math.floor((number - wy) * scale + sy);
}
// Inverse does the reverse of a calculation. Like (3 - 1) * 5 = 10 the inverse is 10 * (1/5) + 1 = 3
// multiply become 1 over ie *5 becomes * 1/5 (or just /5)
// Adds become subtracts and subtract become add.
// and what is first become last and the other way round.
// inverse function converts from screen pixel coord to world coord
function zoomedX_INV(number) { // scale & origin INV
return Math.floor((number - sx) * (1 / scale) + wx);
// or return Math.floor((number - sx) / scale + wx);
}
function zoomedY_INV(number) { // scale & origin INV
return Math.floor((number - sy) * (1 / scale) + wy);
// or return Math.floor((number - sy) / scale + wy);
}
// draw everything in pixels coords
function draw() {
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
context.rect(zoomedX(50), zoomedY(50), zoomed(100), zoomed(100));
context.fillStyle = 'skyblue';
context.fill();
context.beginPath();
context.arc(zoomedX(350), zoomedY(250), zoomed(50), 0, 2 * Math.PI, false);
context.fillStyle = 'green';
context.fill();
}
// wheel event must not be passive to allow default action to be prevented
canvas.addEventListener("wheel", trackWheel, {passive:false});
canvas.addEventListener("mousemove", move)
canvas.addEventListener("mousedown", move)
canvas.addEventListener("mouseup", move)
canvas.addEventListener("mouseout", move) // to stop mouse button locking up
function move(event) { // mouse move event
if (event.type === "mousedown") {
mouse.button = 1;
}
else if (event.type === "mouseup" || event.type === "mouseout") {
mouse.button = 0;
}
mouse.bounds = canvas.getBoundingClientRect();
mouse.x = event.clientX - mouse.bounds.left;
mouse.y = event.clientY - mouse.bounds.top;
var xx = mouse.rx; // get last real world pos of mouse
var yy = mouse.ry;
mouse.rx = zoomedX_INV(mouse.x); // get the mouse real world pos via inverse scale and translate
mouse.ry = zoomedY_INV(mouse.y);
if (mouse.button === 1) { // is mouse button down
wx -= mouse.rx - xx; // move the world origin by the distance
// moved in world coords
wy -= mouse.ry - yy;
// recaculate mouse world
mouse.rx = zoomedX_INV(mouse.x);
mouse.ry = zoomedY_INV(mouse.y);
}
draw();
}
function trackWheel(e) {
if (e.deltaY < 0) {
scale = Math.min(5, scale * 1.1); // zoom in
} else {
scale = Math.max(0.1, scale * (1 / 1.1)); // zoom out is inverse of zoom in
}
wx = mouse.rx; // set world origin
wy = mouse.ry;
sx = mouse.x; // set screen origin
sy = mouse.y;
mouse.rx = zoomedX_INV(mouse.x); // recalc mouse world (real) pos
mouse.ry = zoomedY_INV(mouse.y);
draw();
e.preventDefault(); // stop the page scrolling
}
draw();
body {
background: gainsboro;
margin: 0;
}
canvas {
background: white;
box-shadow: 1px 1px 1px rgba(0, 0, 0, .2);
}
<canvas id="canvas"></canvas>