HTML Canvas,缩放后的鼠标位置和翻译 [英] HTML Canvas, mouse position after scale and translate

查看:204
本文介绍了HTML Canvas,缩放后的鼠标位置和翻译的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在我的画布中实现了缩放功能,就像这样:放大点(使用比例和翻译)



现在我需要计算鼠标在画布上的位置尝试像这样:

  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元素交互?如果是,您有任何经验吗?

解决方案

反矩阵 b

这个答案包括旋转,因为缩放是矩阵中的旋转的一部分,你不能真正地排除一个或另一个。但你可以忽略旋转(设置为零),只需设置比例和翻译,它做你想要的。



反变换。它基本上与标准2D变换相反。它将要求您跟踪转换,以便您可以创建逆转换,如果您希望使用 ctx.rotation ctx.scale ctx.translate ctx.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; //只是为了更容易输入和读取
var im = invMatrix; //只是为了更容易输入和读取

//创建矩阵的旋转和缩放部分
m [3] = m [0] = Math.cos(rotate)*规模;
m [2] = - (m [1] = Math.sin(rotate)* scale);

//添加翻译
m [4] = x;
m [5] = y;

//计算逆变换

//首先得到x轴和y轴的叉积
cross = m [0] * m [3] - m [1] * m [2];

//现在得到反转轴
im [0] = m [3] / cross;
im [1] = -m [1] / cross;
im [2] = -m [2] / cross;
im [3] = m [0] / cross;
}

使用功能
$ b

使用函数很简单。只需调用位置,比例和旋转的所需值即可。



应用倒数



要从像素空间(屏幕x,y)获取世界坐标(变换后的坐标),您需要应用反变换

  function toWorld(x,y){
var xx,yy,m,result;
m = invMatrix;
xx = x - matrix [4]; //删除翻译
yy = y - matrix [5]; //通过减去原点
//通过将xx,yy乘以逆矩阵返回点{x:?,y:?}。
return {
x:xx * m [0] + yy * m [2],
y:xx * m [1] + yy * m [3]
}
}

所以如果你想在世界空间中的鼠标位置

  var mouseWorldSpace = toWorld(mouse.x,mouse.y); //获取鼠标的世界空间坐标

该函数将转换屏幕空间中的任何坐标到世界空间中的正确坐标。



设置2D上下文变换



要使用变换, 2D上下文转换直接用

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

演示



和一个演示,以显示它在使用。很多额外的代码,但我相信你可以找到你需要的部分。 Demo使用 createMatrix 通过旋转,缩放和翻译来转换变换,然后使用 toWorld 将鼠标坐标转换为世界空间。



  //演示functionvar demo = function ** fullScreenCanvas.js begin ** / //在顶部创建一个完整的文档canvas var canvas =(function(){var canvas = document.getElementById(canv); if(canvas!== null){document.body .removeChild(canvas);} //使用2d上下文创建一个空白图像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 ** / //获取鼠标数据。这是一个通用的鼠标处理程序,我使用了一点点杀了这个例子var canvasMouseCallBack = undefined; //如果需要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,//鼠标在元素bm:[1,2,4,6,5,3],//设置和清除按钮原始位的掩码; 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){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;返回鼠标})(); if(typeof canvas ===undefined){mouse.mouseStart(); } else {mouse.mouseStart(canvas); } / ** MouseFull.js end ** / //一些东西来绘制一个网格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; //一些stuf动画变换var timer = 0; var timerStep = 0.01; // ------------------------------------------------ ---------------------------- //来自答案的代码var matrix = [1,0,0,1,0,0 ]; // normal matrix var invMatrix = [1,0,0,1]; //逆矩阵函数createMatrix(x,y,scale,rotate){var m = matrix; //只是为了更容易输入和读取var im = invMatrix; //只是为了更容易输入和读取//创建矩阵的缩放和旋转部分m [3] = m [0] = Math.cos(rotate)* scale; m [2] =  - (m [1] = Math.sin(rotate)* scale); // translation m [4] = x; M [5] = y; //计算逆变换//首先得到x轴和y轴的叉积cross = m [0] * m [3]  -  m [1] * m [2] //现在得到反转轴im [0] = m [3] / cross; im [1] = -m [1] / cross; im [2] = -m [2] / cross; im [3] = m [0] / cross; } //函数转换为世界空间函数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]}} // ------------- -------------------------------------------------- ------------- // canvas的中心var cw = canvas.width / 2; var ch = canvas.height / 2; //主循环函数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); //清除画布//动画化转换计时器+ = 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 // --------------------------------------------- ------------------------- //在x,y scale = s和旋转时间创建矩阵/ createMatrix(x,y,s,定时器/ 3); //使用创建的矩阵设置变换var m = matrix; ctx.setTransform(m [0],m [1],m [2],m [3],m [4],m [5]); // ------------------------------------------------ ---------------------------- //绘制网格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  


I implemented a zoom function in my canvas just like this one: Zoom in on a point (using scale and translate)

Now I need to calculate the position of the mouse in relation to the canvas, I first tried like this:

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

This works excellent until I zoom... I tried to do it like this:

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 ;

Any hint ? Or should I better use a JS library to interact with the canvas element ? If so, do you have any experience ?

解决方案

Inverse Matrix

This answer include rotation as well because the scale is part of the rotation in the matrix you can't really exclude one or the other. But you can ignore the rotation (set it as zero) and just set scale and translation and it does what you want.

The inverse transform. It basically does the reverse of the standard 2D transformations. It will require that you keep track of the transformations so you can create the inverse transform, this can prove problematic in complex transforms if you wish to use ctx.rotation, ctx.scale, ctx.translate or ctx.transform. As you requirements are simple I have created a simple function to do the minimum transformation.

The following creates both the transformation matrix and the inverse transform as two arrays called matrix and invMatrix. The arguments are translation x,y (in canvas coordinates), scale, and rotation.

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

Using the function

To use the function is simple. Just call with the desired values for position, scale and rotation.

Applying the inverse

To get the world coordinates (the transformed coordinates) from a pixel space (screen x, y) you need to apply the inverse transform

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

So if you want the mouse position in world space

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

The function will convert any coordinate that is in screen space to the correct coordinate in world space.

Setting the 2D context transform

To use the transform you can set the 2D context transformation directly with

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

Demo

And a demo to show it in use. A lot of extra code but I am sure you can find the parts you need. The Demo animates the transformation by rotating, scaling, and translating using createMatrix then uses toWorld to convert the mouse coordinates to the world space.

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

这篇关于HTML Canvas,缩放后的鼠标位置和翻译的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆