2016-01-04 29 views
0

我在我的畫布中實現變焦功能,就像這樣:Zoom in on a point (using scale and translate)HTML畫布,規模後鼠標位置和翻譯

現在我需要計算相對於畫布上鼠標的位置,我第一次嘗試像這樣的:

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); 

,直到我放大......我試圖做這樣這個做工精良:

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 ; 

任何提示?或者我應該更好地使用JS庫來與canvas元素進行交互?如果是這樣,你有什麼經驗嗎?

回答

3

逆矩陣

這回答也包括輪換,因爲規模是矩陣中輪換的一部分,所以你不能真正排除一個或那個其他。但是你可以忽略旋轉(將其設置爲零)並設置縮放和翻譯,並且它可以做你想要的。

逆變換。它基本上做了與標準2D轉換相反的操作。這將需要您跟蹤轉換,以便創建反轉換,如果您希望使用ctx.rotationctx.scalectx.translatectx.transform,這可能會證明在複雜轉換中存在問題。由於您的要求很簡單,我創建了一個簡單的函數來執行最小化轉換。

下面創建了兩個變換矩陣和逆變換,分別稱爲matrix和invMatrix。參數是平移x,y(在畫布座標中),比例和旋轉。

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; 
} 

使用功能

要使用的功能非常簡單。只需調用所需的位置,比例和旋轉值即可。

運用逆

要想從一個像素空間則需要申請世界座標(轉換後的座標)(屏幕X,Y)逆變換

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] 
    } 
} 

所以,如果你希望鼠標在世界空間中的位置

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

該函數將轉換屏幕空間中的任何座標到世界空間的正確座標。

設置2D語境轉換

要使用變換,你可以直接與

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

演示

並演示來顯示它在使用中設定的2D背景下轉型。很多額外的代碼,但我相信你可以找到你需要的部分。演示通過使用createMatrix進行旋轉,縮放和平移來動畫化轉換,然後使用toWorld將鼠標座標轉換爲世界空間。

// 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; 
 
      m.x = e.offsetX; m.y = e.offsetY; 
 
      if (m.x === undefined) { m.x = e.clientX; m.y = 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") { m.w = e.wheelDelta; 
 
      } else if (t === "DOMMouseScroll") { m.w = -e.detail;} 
 
      if (canvasMouseCallBack) { canvasMouseCallBack(m.x, m.y); } 
 
      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)

2

一步一步:

查找鼠標的畫布上的座標:

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

規範化那些座標,以便它們在[0; 1]:

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

現在說你查看的是一個矩形定義的名爲... well ... viewRect,鼠標在視圖中的位置是:

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

當您啓動您的應用程序時,矩形爲0,0,canvasWidth,canvasHeight。
當你點擊,你必須調整你的矩形。

如果單擊手段viewX,空想的由zFactor縮放,代碼如下:

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; 

您的Draw方法應該是這樣的:

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

... draw 

context.restore();