简体   繁体   中英

Zoom into point in canvas

I am trying to zoom into a given point and i honestly cannot understand what i am doing wrong.

First i get the mouse point and use the canvas transform matrix to get the point in canvas context ( https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-gettransform ).

const domPoint = new window.DOMPoint(event.offsetX, event.offsetY);
const currMatrix = this.canvasManager.ctx.getTransform();
const canvasPoint = domPoint.matrixTransform(currMatrix);

Then i translate, scale and translate back using the matrix returned in getTransform (DOMMatrix object).

const m = currMatrix
.translateSelf(canvasPoint.x, canvasPoint.y)
.scaleSelf(scale, scale)
.translateSelf(-canvasPoint.x, -canvasPoint.y);

Finally i set the transform using the last matrix.

this.ctx.setTransform(this.zoom.matrix)

The method i am using is based on one stack overflow answer .

Here's my code

 var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); var currMatrix = ctx.getTransform(); var plusBtn = document.getElementById("plus"); var minusBtn = document.getElementById("minus"); var infoP = document.getElementById("info"); var zoom; var scale = 1; const scaleFactor = 0.1; function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.setTransform(currMatrix) ctx.fillStyle = "blue"; ctx.fillRect(50, 50, 100, 100); } draw() function getDomPoint(event) { const offSetCanvasLeft = canvas.getBoundingClientRect().left; const offSetCanvasTop = canvas.getBoundingClientRect().top; return { x: event.pageX - offSetCanvasLeft, y: event.pageY - offSetCanvasTop, } } function domToCanvasPoint(point) { const domPoint = new window.DOMPoint(point.x, point.y); return domPoint.matrixTransform(currMatrix); } function updateScale() { scale = zoom === 'in' ? scale + scaleFactor : scale - scaleFactor ; setDebugInfo(scale) } function zoomMatrixIntoPoint(point) { currMatrix = currMatrix .translateSelf(point.x, point.y) .scaleSelf(scale, scale) .translateSelf(-point.x, -point.y); } function setDebugInfo(msg) { infoP.innerHTML = msg; } canvas.addEventListener('mousedown', function(event) { const domPoint = getDomPoint(event); const canvasPoint = domToCanvasPoint(domPoint); updateScale(); zoomMatrixIntoPoint(canvasPoint); draw() }, false); plusBtn.addEventListener('click', function(event) { zoom = 'in' setDebugInfo(`ZOOM IN with scale ${scale}`) }, false); minusBtn.addEventListener('click', function(event) { zoom = 'out' setDebugInfo(`ZOOM OUT with scale ${scale}`) }, false); 
 <canvas id="myCanvas" width="300" height="300" style="border:1px solid black"></canvas> <button id="plus">+</button> <button id="minus">-</button> <p id="info">info here!</p> 

Would appreciate any help. Thank you.

Your issue is a simple logical error.

You are keeping curMatrix and update it every time. This means that all the values you do pass to translateSelf and scaleSelf are relative to the previous values.

However, when you do scale -= scaleFactor or scale += scaleFactor , scale is the absolute scale value.
So when you use it later in scaleSelf , you are quickly setting an huge absolute scale value, which won't get down until it becomes less than 1 .

(1.1 * 1.2 * 1.3 * 1.4 * 1.5 * 1.6) => real scale is 5.8

and

(1.1 * 1.2 * 1.3 * 1.2 * 1.1 * 1) => real scale is 2.3
                 ^-- click zoom-out

So all you need to fix is the line which sets this scale value: Instead of incrementing this value, set it always 1 based.

scale = zoom === 'in' ? 1 + scaleFactor : 1 - scaleFactor ;

 var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); var currMatrix = ctx.getTransform(); var plusBtn = document.getElementById("plus"); var minusBtn = document.getElementById("minus"); var infoP = document.getElementById("info"); var zoom; var scale = 1; const scaleFactor = 0.1; function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.setTransform(currMatrix) ctx.fillStyle = "blue"; ctx.fillRect(50, 50, 100, 100); } draw() function getDomPoint(event) { const offSetCanvasLeft = canvas.getBoundingClientRect().left; const offSetCanvasTop = canvas.getBoundingClientRect().top; return { x: event.pageX - offSetCanvasLeft, y: event.pageY - offSetCanvasTop, } } function domToCanvasPoint(point) { const domPoint = new window.DOMPoint(point.x, point.y); return domPoint.matrixTransform(currMatrix); } function updateScale() { scale = zoom === 'in' ? 1 + scaleFactor : 1 - scaleFactor; setDebugInfo(scale) } function zoomMatrixIntoPoint(point) { currMatrix .translateSelf(point.x, point.y) .scaleSelf(scale, scale) .translateSelf(-point.x, -point.y); } function setDebugInfo(msg) { infoP.innerHTML = msg; } canvas.addEventListener('mousedown', function(event) { const domPoint = getDomPoint(event); const canvasPoint = domToCanvasPoint(domPoint); updateScale(); zoomMatrixIntoPoint(canvasPoint); draw() }, false); plusBtn.addEventListener('click', function(event) { zoom = 'in' setDebugInfo(`ZOOM IN with scale ${scale}`) }, false); minusBtn.addEventListener('click', function(event) { zoom = 'out' setDebugInfo(`ZOOM OUT with scale ${scale}`) }, false); 
 <canvas id="myCanvas" width="300" height="300" style="border:1px solid black"></canvas> <button id="plus">+</button> <button id="minus">-</button> <p id="info">info here!</p> 

Also note that DOMMatrix#scale() accepts optional origin arguments, which would allow you to avoid the two transform calls:

 var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); var currMatrix = ctx.getTransform(); var plusBtn = document.getElementById("plus"); var minusBtn = document.getElementById("minus"); var infoP = document.getElementById("info"); var zoom; var scale = 1; const scaleFactor = 0.1; function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.setTransform(currMatrix) ctx.fillStyle = "blue"; ctx.fillRect(50, 50, 100, 100); } draw() function getDomPoint(event) { const offSetCanvasLeft = canvas.getBoundingClientRect().left; const offSetCanvasTop = canvas.getBoundingClientRect().top; return { x: event.pageX - offSetCanvasLeft, y: event.pageY - offSetCanvasTop, } } function domToCanvasPoint(point) { const domPoint = new window.DOMPoint(point.x, point.y); return domPoint.matrixTransform(currMatrix); } function updateScale() { scale = zoom === 'in' ? 1 + scaleFactor : 1 - scaleFactor; setDebugInfo(scale) } function zoomMatrixIntoPoint(point) { currMatrix // scaleSelf(scaleX, scaleY, scaleZ, originX, originY, originZ) .scaleSelf(scale, scale, 1, point.x, point.y, 0) } function setDebugInfo(msg) { infoP.innerHTML = msg; } canvas.addEventListener('mousedown', function(event) { const domPoint = getDomPoint(event); const canvasPoint = domToCanvasPoint(domPoint); updateScale(); zoomMatrixIntoPoint(canvasPoint); draw() }, false); plusBtn.addEventListener('click', function(event) { zoom = 'in' setDebugInfo(`ZOOM IN with scale ${scale}`) }, false); minusBtn.addEventListener('click', function(event) { zoom = 'out' setDebugInfo(`ZOOM OUT with scale ${scale}`) }, false); 
 <canvas id="myCanvas" width="300" height="300" style="border:1px solid black"></canvas> <button id="plus">+</button> <button id="minus">-</button> <p id="info">info here!</p> 

And if you need all your values to be absolute (ie translate too), then simply create a new DOMMatrix every time, and here keep your original scale increment:

 var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); var currMatrix = ctx.getTransform(); var plusBtn = document.getElementById("plus"); var minusBtn = document.getElementById("minus"); var infoP = document.getElementById("info"); var zoom; var scale = 1; const scaleFactor = 0.1; function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.setTransform(currMatrix) ctx.fillStyle = "blue"; ctx.fillRect(50, 50, 100, 100); } draw() function getDomPoint(event) { const offSetCanvasLeft = canvas.getBoundingClientRect().left; const offSetCanvasTop = canvas.getBoundingClientRect().top; return { x: event.pageX - offSetCanvasLeft, y: event.pageY - offSetCanvasTop, } } function domToCanvasPoint(point) { const domPoint = new window.DOMPoint(point.x, point.y); return domPoint.matrixTransform(currMatrix); } function updateScale() { scale = zoom === 'in' ? scale + scaleFactor : scale - scaleFactor; setDebugInfo(scale) } function zoomMatrixIntoPoint(point) { // create a new DOMMatrix currMatrix = new DOMMatrix() .scaleSelf(scale, scale, 1, point.x, point.y, 0) } function setDebugInfo(msg) { infoP.innerHTML = msg; } canvas.addEventListener('mousedown', function(event) { const domPoint = getDomPoint(event); const canvasPoint = domToCanvasPoint(domPoint); updateScale(); zoomMatrixIntoPoint(canvasPoint); draw() }, false); plusBtn.addEventListener('click', function(event) { zoom = 'in' setDebugInfo(`ZOOM IN with scale ${scale}`) }, false); minusBtn.addEventListener('click', function(event) { zoom = 'out' setDebugInfo(`ZOOM OUT with scale ${scale}`) }, false); 
 <canvas id="myCanvas" width="300" height="300" style="border:1px solid black"></canvas> <button id="plus">+</button> <button id="minus">-</button> <p id="info">info here!</p> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM