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

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

问题描述

我在画布中实现了缩放功能,就像这样:放大一个点(使用缩放和平移)

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 ;

有什么提示吗?或者我应该更好地使用 JS 库与画布元素进行交互?如果是这样,你有什么经验吗?

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

推荐答案

逆矩阵

这个答案也包括旋转,因为比例是矩阵中旋转的一部分,你不能真正排除其中一个.但是您可以忽略旋转(将其设置为零),只需设置缩放和平移即可.

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.

逆变换.它基本上与标准的 2D 转换相反.它将要求您跟踪转换,以便创建逆转换,如果您希望使用 ctx.rotationctx.scale、ctx.translatectx.transform.由于您的要求很简单,我创建了一个简单的函数来进行最小转换.

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.

下面将变换矩阵和逆变换创建为两个称为矩阵和 invMatrix 的数组.参数是平移 x、y(在画布坐标中)、缩放和旋转.

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

使用功能

使用该功能很简单.只需调用所需的位置、缩放和旋转值即可.

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

应用逆

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

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.

设置 2D 上下文变换

要使用转换,您可以直接设置 2D 上下文转换

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 使用 createMatrix 通过旋转、缩放和平移来动画转换,然后使用 toWorld 将鼠标坐标转换为世界空间.

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 world 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天全站免登陆