Tela HTML, posizione del mouse dopo la scala e traduzione

Ho implementato una funzione di zoom nella mia canvas proprio come questa: Ingrandisci su un punto (usando la scala e traduci)

Ora ho bisogno di calcolare la posizione del mouse in relazione alla canvas, ho provato prima in questo modo:

var rect = this._canvas.getBoundingClientRect(); var x = ((event.clientX - rect.left) / (rect.right - rect.left) * this._canvas.width); var y = ((event.clientY - rect.top) / (rect.bottom - rect.top) * this._canvas.height); 

Funziona perfettamente finché non eseguo lo zoom … Ho provato a farlo in questo modo:

 var x = ((event.clientX - rect.left) / (rect.right - rect.left) * this._canvas.width) - this._canvas.offsetLeft ; var y = ((event.clientY - rect.top) / (rect.bottom - rect.top) * this._canvas.height) - offset.top this._canvas.offSetTop ; 

Qualche suggerimento? O dovrei usare meglio una libreria JS per interagire con l’elemento canvas? Se è così, hai qualche esperienza?

Matrice inversa

Questa risposta include anche la rotazione perché la scala fa parte della rotazione nella matrice che non puoi davvero escludere l’una o l’altra. Ma puoi ignorare la rotazione (impostala come zero) e solo impostare la scala e la traduzione e fa ciò che vuoi.

La trasformazione inversa. Fondamentalmente fa il contrario delle trasformazioni 2D standard. Richiederà di tenere traccia delle trasformazioni in modo da poter creare la trasformazione inversa, questo può rivelarsi problematico in trasformazioni complesse se si desidera utilizzare ctx.rotation , ctx.scale , ctx.translate o ctx.transform . Poiché i requisiti sono semplici, ho creato una semplice funzione per eseguire la trasformazione minima.

Quanto segue crea sia la matrice di trasformazione che la trasformazione inversa come due matrici chiamate matrice e invMatrix. Gli argomenti sono traduzione x, y (in coordinate canvas), scala e rotazione.

 var matrix = [1,0,0,1,0,0]; var invMatrix = [1,0,0,1]; function createMatrix(x, y, scale, rotate){ var m = matrix; // just to make it easier to type and read var im = invMatrix; // just to make it easier to type and read // create the rotation and scale parts of the matrix m[3] = m[0] = Math.cos(rotate) * scale; m[2] = -(m[1] = Math.sin(rotate) * scale); // add the translation m[4] = x; m[5] = y; // calculate the inverse transformation // first get the cross product of x axis and y axis cross = m[0] * m[3] - m[1] * m[2]; // now get the inverted axis im[0] = m[3] / cross; im[1] = -m[1] / cross; im[2] = -m[2] / cross; im[3] = m[0] / cross; } 

Usando la funzione

Utilizzare la funzione è semplice. Basta chiamare con i valori desiderati per posizione, scala e rotazione.

Applicando l’inverso

Per ottenere le coordinate del mondo (le coordinate trasformate) da uno spazio di pixel (schermo x, y) è necessario applicare la trasformazione inversa

 function toWorld(x,y){ var xx, yy, m, result; m = invMatrix; xx = x - matrix[4]; // remove the translation yy = y - matrix[5]; // by subtracting the origin // return the point {x:?,y:?} by multiplying xx,yy by the inverse matrix return { x: xx * m[0] + yy * m[2], y: xx * m[1] + yy * m[3] } } 

Quindi se vuoi la posizione del mouse nello spazio del mondo

 var mouseWorldSpace = toWorld(mouse.x,mouse.y); // get the world space coordinates of the mouse 

La funzione convertirà tutte le coordinate che si trovano nello spazio dello schermo alla coordinata corretta nello spazio del mondo.

Impostazione della trasformazione del contesto 2D

Per utilizzare la trasformazione è ansible impostare direttamente la trasformazione del contesto 2D

 var m = matrix; ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5]); 

dimostrazione

E una demo per mostrarlo in uso. Un sacco di codice in più, ma sono sicuro che puoi trovare le parti di cui hai bisogno. La Demo anima la trasformazione ruotando, ridimensionando e traducendo usando createMatrix quindi usa toWorld per convertire le coordinate del mouse nello spazio del mondo.

 // the demo function var demo = function(){ /** fullScreenCanvas.js begin **/ // create a full document canvas on top var canvas = (function(){ var canvas = document.getElementById("canv"); if(canvas !== null){ document.body.removeChild(canvas); } // creates a blank image with 2d context canvas = document.createElement("canvas"); canvas.id = "canv"; canvas.width = window.innerWidth; canvas.height = window.innerHeight; canvas.style.position = "absolute"; canvas.style.top = "0px"; canvas.style.left = "0px"; canvas.style.zIndex = 1000; canvas.ctx = canvas.getContext("2d"); document.body.appendChild(canvas); return canvas; })(); var ctx = canvas.ctx; /** fullScreenCanvas.js end **/ /** MouseFull.js begin **/ // get the mouse data . This is a generic mouse handler I use so a little over kill for this example var canvasMouseCallBack = undefined; // if needed var mouse = (function(){ var mouse = { x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false, interfaceId : 0, buttonLastRaw : 0, buttonRaw : 0, over : false, // mouse is over the element bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits; getInterfaceId : function () { return this.interfaceId++; }, // For UI functions startMouse:undefined, }; function mouseMove(e) { var t = e.type, m = mouse; mx = e.offsetX; my = e.offsetY; if (mx === undefined) { mx = e.clientX; my = e.clientY; } m.alt = e.altKey;m.shift = e.shiftKey;m.ctrl = e.ctrlKey; if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1]; } else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2]; } else if (t === "mouseout") { m.buttonRaw = 0; m.over = false; } else if (t === "mouseover") { m.over = true; } else if (t === "mousewheel") { mw = e.wheelDelta; } else if (t === "DOMMouseScroll") { mw = -e.detail;} if (canvasMouseCallBack) { canvasMouseCallBack(mx, my); } e.preventDefault(); } function startMouse(element){ if(element === undefined){ element = document; } "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",").forEach( function(n){element.addEventListener(n, mouseMove);}); element.addEventListener("contextmenu", function (e) {e.preventDefault();}, false); } mouse.mouseStart = startMouse; return mouse; })(); if(typeof canvas === "undefined"){ mouse.mouseStart(); }else{ mouse.mouseStart(canvas); } /** MouseFull.js end **/ // some stuff to draw a grid var gridStart= -(canvas.width/10)*4; var gridEnd = (canvas.width/10)*14; var gridStepMajor = canvas.width/10; var gridStepMinor = canvas.width/20; var minorCol = "#999"; var majorCol = "#000"; var minorWidth = 1; var majorWidth = 3; // some stuf to animate the transformation var timer = 0; var timerStep = 0.01; //---------------------------------------------------------------------------- // the code from the answer var matrix = [1, 0, 0, 1, 0, 0]; // normal matrix var invMatrix = [1, 0, 0, 1]; // inverse matrix function createMatrix(x, y, scale, rotate){ var m = matrix; // just to make it easier to type and read var im = invMatrix; // just to make it easier to type and read // create the scale and rotation part of the matrix m[3] = m[0] = Math.cos(rotate) * scale; m[2] = -(m[1] = Math.sin(rotate) * scale); // translation m[4] = x; m[5] = y; // calculate the inverse transformation // first get the cross product of x axis and y axis cross = m[0] * m[3] - m[1] * m[2]; // now get the inverted axies im[0] = m[3] / cross; im[1] = -m[1] / cross; im[2] = -m[2] / cross; im[3] = m[0] / cross; } // function to transform to world space function toWorld(x,y){ var xx, yy, m; m = invMatrix; xx = x - matrix[4]; yy = y - matrix[5]; return { x: xx * m[0] + yy * m[2] , y: xx * m[1] + yy * m[3] } } //---------------------------------------------------------------------------- // center of canvas var cw = canvas.width / 2; var ch = canvas.height / 2; // the main loop function update(){ var i,x,y,s; ctx.setTransform(1, 0, 0, 1, 0, 0); // reset the transform so we can clear ctx.clearRect(0, 0, canvas.width, canvas.height); // clear the canvas // animate the transformation timer += timerStep; x = Math.cos(timer) * gridStepMajor * 5 + cw; // position y = Math.sin(timer) * gridStepMajor * 5 + ch; s = Math.sin(timer/1.2) + 1.5; // scale //---------------------------------------------------------------------- // create the matrix at x,y scale = s and rotation time/3 createMatrix(x,y,s,timer/3); // use the created matrix to set the transformation var m = matrix; ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5]); //---------------------------------------------------------------------------- //draw a grid ctx.lineWidth = 2; ctx.beginPath(); ctx.strokeStyle = majorCol ; ctx.lineWidth = majorWidth; for(i = gridStart; i <= gridEnd; i+= gridStepMajor){ ctx.moveTo(gridStart, i); ctx.lineTo(gridEnd, i); ctx.moveTo(i, gridStart); ctx.lineTo(i, gridEnd); } ctx.stroke(); ctx.strokeStyle = minorCol ; ctx.lineWidth = minorWidth; for(i = gridStart+gridStepMinor; i < gridEnd; i+= gridStepMinor){ ctx.moveTo(gridStart, i); ctx.lineTo(gridEnd, i); ctx.moveTo(i, gridStart); ctx.lineTo(i, gridEnd); } ctx.stroke(); //--------------------------------------------------------------------- // get the mouse world coordinates var mouseWorldPos = toWorld(mouse.x, mouse.y); //--------------------------------------------------------------------- // marke the location with a cross and a circle; ctx.strokeStyle = "red"; ctx.lineWidth = 3; ctx.beginPath(); ctx.moveTo(mouseWorldPos.x - gridStepMajor, mouseWorldPos.y) ctx.lineTo(mouseWorldPos.x + gridStepMajor, mouseWorldPos.y) ctx.moveTo(mouseWorldPos.x, mouseWorldPos.y - gridStepMajor) ctx.lineTo(mouseWorldPos.x, mouseWorldPos.y + gridStepMajor) ctx.stroke(); ctx.fillStyle = "red"; ctx.strokeStyle = "yellow"; ctx.lineWidth = 4; ctx.beginPath(); ctx.arc(mouseWorldPos.x, mouseWorldPos.y, 6, 0, Math.PI*2); ctx.fill(); ctx.stroke(); ctx.fillStyle = "Blue"; ctx.setTransform(1,0,0,1,0,0); ctx.font = "18px Arial"; var str = "Mouse canvas X: "+ mouse.x + " Y: " + mouse.y; ctx.fillText(str , 10 ,18); var str = "Mouse word X: "+ mouseWorldPos.x.toFixed(2) + " Y: " + mouseWorldPos.y.toFixed(2); ctx.fillText(str , 10 ,36); // if not over request a new animtion frame if(!endItAll){ requestAnimationFrame(update); }else{ // if done remove the canvas var can = document.getElementById("canv"); if(can !== null){ document.body.removeChild(can); } // flag that we are ready to start again endItAll = false; } } update(); // start the animation } // Flag to indicate that the current execution should shut down var endItAll = false; // resizes but waits for the current running animnation to shut down function resizeIt(){ endItAll = true; function waitForIt(){ if(!endItAll){ demo(); }else{ setTimeout(waitForIt, 100); } } setTimeout(waitForIt, 100); } // starts the demo demo(); // listen to resize events and resize canvas if needed window.addEventListener("resize",resizeIt) 

Vai passo dopo passo:

Trova le coordinate del mouse sulla canvas:

 var rect = canvas.getBoundingClientRect(); var xMouse = event.clientX - rect.left; var yMouse = event.clientY - rect.top; 

Normalizza quelle coordinate in modo che siano in [0; 1]:

 var relX = xMouse / canvas.width; var relY = yMouse / canvas.height; 

ora dite che la vostra vista è definita da un rect chiamato … beh … viewRect, la posizione del mouse nella vista è:

 var viewX = viewRect.left + relX*(viewRect.right-viewRect.left); var viewY = viewRect.top + relY*(viewRect.bottom-viewRect.top); 

Quando avvii la tua app il tuo rect è 0,0, canvasWidth, canvasHeight.
Quando fai clic, devi regolare il tuo rect.

Se fare clic significa zoomare da zFactor a viewX, viewY, il codice sarà simile a:

 var newWidth = viewRect.width/zFactor; var newHeight = viewRect.height/zFactor; viewRect.left = viewX - newWidth/2; viewRect.right = viewX + newWidth/2; viewRect.top = viewY - newHeight/2; viewRect.bottom = viewY + newHeight/2; 

il tuo metodo di disegno dovrebbe assomigliare a:

 context.save(); context.translate((viewRect.left+viewRect.right )/ 2, ...) ; var scaleFactor = (viewRect.right+viewRect.left ) / canvasWidth; context.scale(scaleFactor, scaleFactor); ... draw context.restore();