缩放和平移画布后,鼠标坐标不匹配 [英] Mouse coordinates don't match after Scaling and Panning canvas

查看:210
本文介绍了缩放和平移画布后,鼠标坐标不匹配的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对JavaScript和canvas非常陌生,我有一个应用程序可以检测椭圆路径上动画的元素。它后来去形成一棵树..但这是我连接到jsfiddle的基本结构。
无需缩放或平移就可以正常工作,但只要我尝试缩放或平移,鼠标坐标就会失灵。
我尝试按照 HTML5画布获取后的坐标获得markE的建议并翻译
但我肯定做错了什么,我显然不了解画布和转换矩阵发生了什么。我花了大约3天的时间试图改变我能想到的所有组合,但我似乎无法弄清楚:s

已解决

strong>:
这里是我的代码,包含缩放和鼠标平移以及动画和检测椭圆上的元素:
http://jsfiddle.net/metalloyd/A8hgz/

  theCanvas = document.getElementById( canvasOne); 
context = theCanvas.getContext(2d);
var status = document.getElementById('status');
var $ canvas = $(#canvasOne);
var canvasOffset = $ canvas.offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var scrollX = $ canvas.scrollLeft();
var scrollY = $ canvas.scrollTop();
var cw = theCanvas.width;
var ch = theCanvas.height;
var scaleFactor = 1.00;
var panX = 0;
var panY = 0;

var mainX = 250;
//设置中间点位置X值
var mainY = 100;
//设置中间点位置Y值
var mainR = 125;
//主椭圆半径R
var no = 5;
//要显示的节点数
var div_angle = 360 / no;

var circle = {
centerX:mainX,
centerY:mainY + 100,
radius:mainR,
角度:.9
};

var ball = {
x:0,
y:0,
speed:.1
};
var a = 1.8;
//椭圆宽度
var b = .5;
//椭圆高度

//比例和平移变量
var translatePos = {
x:1,
y:1
};
var startDragOffset = {};
var mouseDown = false;

var elements = [{}];

// Animate
var animateInterval = setInterval(drawScreen,1);

//动画
函数drawScreen(){
context.clearRect(0,0,cw,ch);
//背景框
context.beginPath();
context.fillStyle ='#EEEEEE';
context.fillRect(0,0,theCanvas.width,theCanvas.height);
context.strokeRect(1,1,theCanvas.width - 2,theCanvas.height - 2);
context.closePath();

context.save();
context.translate(panX,panY);
context.scale(scaleFactor,scaleFactor);

ball.speed = ball.speed + 0.001;

for(var i = 1; i <= no; i ++){
// male
new_angle = div_angle * i;
//开始在椭圆上不同点的球1的位置
circle.angle =(new_angle *(0.0174532925))+ ball.speed;
//椭圆x位置和y位置用于第一个球的动画
// xx和yy记录第一个球坐标
xx = ball.x = circle.centerX - (a * Math .cos(circle.angle))*(circle.radius);
yy = ball.y = circle.centerY +(b * Math.sin(circle.angle))*(circle.radius);
//绘制位置x和y的第一个球
context.fillStyle =#000000;
context.beginPath();
context.arc(ball.x,ball.y,10,Math.PI * 2,true);
context.fill();
context.closePath();

// alert(male Positions+X:+ ball.x +Y:+ ball.y);

//女性
new_angle = div_angle * i + 4;
//开始在椭圆上不同点的球2的位置
circle.angle =(new_angle *(0.0174532925))+ ball.speed;
//第二个球的动画的椭圆x位置和y位置
//ball.x和ball.y记录第二个球位置
ball.x = circle.centerX - (a * Math.cos(circle.angle))*(circle.radius);
ball.y = circle.centerY +(b * Math.sin(circle.angle))*(circle.radius);
context.fillStyle =#000000;
context.beginPath();
context.arc(ball.x,ball.y,10,Math.PI * 2,true);
context.fill();
context.closePath();

// alert(女性职位+X:+ ball.x +Y:+ ball.y);

//记录元素数组中的球位置,用鼠标坐标定位位置。
elements [i] = {
id:i,
femaleX:ball.x,
femaleY:ball.y,
maleX:xx,
maleY:yy,
w:10 //找到位置时要抽出的球的半径
};
//文本编号
context.beginPath();
context.fillStyle =blue;
context.font =bold 16px Arial;
context.fillText(elements [i] .id,ball.x - 20,ball.y + 20);
context.closePath();
//线画 - 将线从中心连接到球。
context.moveTo(mainX,mainY);
context.lineTo((ball.x + xx)/ 2,(ball.y + yy)/ 2);
//画直线到ball1和ball2之间的中点
context.stroke();
context.fill();
context.closePath();
}
//中心点
context.fillStyle =#000000;
context.beginPath();
context.arc(mainX,mainY,15,0,Math.PI * 2,true);
context.fill();
context.closePath();

context.restore();
}

//事件监听器
//鼠标移动事件以提醒屏幕上球的位置


document.getElementById (plus)。addEventListener(click,function(){
scaleFactor * = 1.1;
drawScreen();
},false);
$ b document.getElementById(minus)。addEventListener(click,function(){
scaleFactor / = 1.1;
drawScreen();
},假);

//处理屏幕平移的事件侦听器
context.canvas.addEventListener(mousedown,function(evt){
mouseDown = true;
startDragOffset.x = evt.clientX - translatePos.x;
startDragOffset.y = evt.clientY - translatePos.y;
});

context.canvas.addEventListener(mouseup,function(evt){
mouseDown = false;
});

context.canvas.addEventListener(mouseover,function(evt){
mouseDown = false;
});

context.canvas.addEventListener(mouseout,function(evt){
mouseDown = false;
});

context.canvas.addEventListener(mousemove,function(evt){
if(mouseDown){
translatePos.x = evt.clientX - startDragOffset.x;
translatePos.y = evt.clientY - startDragOffset.y;

panX = translatePos.x;
panY = translatePos.y;

drawScreen();
}

evt.preventDefault();
evt.stopPropagation();

var mouseX = parseInt(evt.clientX - offsetX);
var mouseY = parseInt(evt.clientY - offsetY);

var mouseXT = parseInt((mouseX - panX)/ scaleFactor);
var mouseYT = parseInt((mouseY - panY) )/ scaleFactor);

status.innerHTML = mouseXT +|+ mouseYT;

for(var i = 1; i< elements.length; i ++){
var b = elements [i];
context.closePath();
context.beginPath();
context.arc(b.femaleX,b.femaleY,10,Math.PI * 2);
context.arc(b.maleX,b.maleY,10,0,Math.PI * 2);

if(context.isPointInPath(mouseXT,mouseYT)){
theCanvas.style.cursor ='pointer';
alert(b.id +female.x:+ b.femaleX +female.y:+ b.femaleY +ball.x:+ ball.x +ball.y:+ ball.y);
返回;
} else theCanvas.style.cursor ='default';
context.closePath();
}

});`


解决方案<在这种情况下使用变换矩阵是有用的或甚至是必要的:

$ ul
  • 如果你深度嵌套变换。

    li>
  • 如果您使用不同的转换改变不同的图形。
  • 如果您需要临时转换坐标,那么
  • 如果您如果您正在进行涉及旋转的转换。



  • 但对于简单的方法是平移和缩放整个画布。这是一种更简单的方法。



    首先,设置变量以保存当前的缩放和平移量:

      var scaleFactor = 1.00; 
    var panX = 0;
    var panY = 0;

    然后使用这些pan&缩放变量以完成所有绘图。




    • 清除画布。

    • save未转换的画布状态。

    • 使用 panX 变量进行翻译。 scaleFactor 变量。

    • 绘制所有元素,就好像它们处于未格式化的空间一样。 >将上下文恢复到未转换状态。



    示例代码:

     函数drawTranslated(){

    ctx.clearRect(0,0,cw,ch);

    ctx.save();
    ctx.translate(panX,panY);
    ctx.scale(scaleFactor,scaleFactor);

    ctx.beginPath();
    ctx.arc(circleX,circleY,15,0,Math.PI * 2);
    ctx.closePath();
    ctx.fillStyle = randomColor();
    ctx.fill();

    ctx.restore();

    }

    现在,关于鼠标坐标:



    浏览器始终以未转换的坐标形式返回鼠标位置。您的绘图已在变换空间中完成。如果你想知道鼠标在转换空间中的位置,可以将未转换的鼠标坐标转换为转换后的坐标,如下所示:

      var mouseXTransformed =(mouseX-panX)/ scaleFactor; 
    var mouseYTransformed =(mouseY-panY)/ scaleFactor;

    以下是示例代码和演示: http://jsfiddle.net/m1erickson/HwNp3/

     <!doctype html> 
    < html>
    < head>
    < link rel =stylesheettype =text / cssmedia =allhref =css / reset.css/> <! - 重置css - >
    < script type =text / javascriptsrc =http://code.jquery.com/jquery.min.js>< / script>
    < style>
    body {background-color:ivory; }
    #canvas {border:1px solid red;}
    < / style>
    < script>
    $(function(){

    var canvas = document.getElementById(canvas);
    var ctx = canvas.getContext(2d);
    var $ canvas = $(#canvas);
    var canvasOffset = $ canvas.offset();
    var offsetX = canvasOffset.left;
    var offsetY = canvasOffset.top;
    var scrollX = $ canvas.scrollLeft();
    var scrollY = $ canvas.scrollTop();
    var cw = canvas.width;
    var ch = canvas.height;

    var scaleFactor = 1.00;
    var panX = 0;
    var panY = 0;

    var circleX = 150;
    var circleY = 150 ;

    var $ screen = $(#screen);
    var $ transformed = $(#transformed);
    var $ trx = $(#trx );

    drawTranslated();

    $(#canvas)。mousemove(function(e){handleMouseMove(e);});
    $(#scaledown)。click(function(){scaleFactor / = 1.1; drawTranslated();});
    $(#scaleup)。click(function(){scaleFactor * = 1.1; drawTranslated ();});
    $(#panleft)。click(function(){panX- = 10; d rawTranslated(); });
    $(#panright)。click(function(){panX + = 10; drawTranslated();});


    函数drawTranslated(){
    ctx.clearRect(0,0,cw,ch);

    ctx.save();
    ctx.translate(panX,panY);
    ctx.scale(scaleFactor,scaleFactor);

    ctx.beginPath();
    ctx.arc(circleX,circleY,15,0,Math.PI * 2);
    ctx.closePath();
    ctx.fillStyle = randomColor();
    ctx.fill();

    ctx.restore();

    $ trx.text(Pan:+ panX +,Scale:+ scaleFactor);
    }

    函数handleMouseMove(e){
    e.preventDefault();
    e.stopPropagation();

    var mouseX = parseInt(e.clientX-offsetX);
    var mouseY = parseInt(e.clientY-offsetY);

    var mouseXT = parseInt((mouseX-panX)/ scaleFactor);
    var mouseYT = parseInt((mouseY-panY)/ scaleFactor);

    $ screen.text(屏幕坐标:+ mouseX +/+ mouseY);

    $ transformed.text(转换后的坐标:+ mouseXT +/+ mouseYT);

    $ b函数randomColor(){
    return('#'+ Math.floor(Math.random()* 16777215).toString(16));
    }

    }); // end $(function(){});
    < / script>
    < / head>
    < body>
    < h3>转换后的坐标是变换后的空间中的鼠标xY。< br>圆心始终在翻译后的[150,150]< / h3>
    < h4 id = screen>屏幕坐标:< / h4>
    < h4 id =转换>转换后的坐标:< / h4>
    < h4 id = trx> Pan&规模和LT; / H4>
    < button id = scaledown>缩小比例< /按钮>
    < button id = scaleup>向上缩放< /按钮>
    < button id = panleft>向左平移< /按钮>
    < button id = panright>平移右< /按>< br>
    < canvas id =canvaswidth = 350 height = 400>< / canvas>
    < / body>
    < / html>


    I'm very new to javascript and canvas and I have a program that's supposed to detect elements animated on an elliptical path. It later on goes to form a tree.. But this is the basic structure that I've linked onto jsfiddle. It works fine without scaling or panning but as soon as I try to scale or Pan, the mouse coordinates go haywire. I tried following markE's advice from HTML5 canvas get coordinates after zoom and translate But i'm definitely doing something wrong and I've clearly not understood whats happening with the canvas and the transformation matrix. I've spent about 3 days trying to change all the combinations I can think of but I can't seem to figure it out :s

    SOLVED: Here's my code with zooming and mouse panning and for animating and detecting elements on an ellipse: http://jsfiddle.net/metalloyd/A8hgz/

                theCanvas = document.getElementById("canvasOne");
                context = theCanvas.getContext("2d");
                var status = document.getElementById('status');
                var $canvas = $("#canvasOne");
                var canvasOffset = $canvas.offset();
                var offsetX = canvasOffset.left;
                var offsetY = canvasOffset.top;
                var scrollX = $canvas.scrollLeft();
                var scrollY = $canvas.scrollTop();
                var cw = theCanvas.width;
                var ch = theCanvas.height;
                var scaleFactor = 1.00;
                var panX = 0;
                var panY = 0;
    
                var mainX = 250;
                // setting the middle point position X value
                var mainY = 100;
                // setting the middle point position Y value
                var mainR = 125;
                // main ellipse radius R
                var no = 5;
                // number of nodes to display
                var div_angle = 360 / no;
    
                var circle = {
                    centerX: mainX,
                    centerY: mainY + 100,
                    radius: mainR,
                    angle: .9
                };
    
                var ball = {
                    x: 0,
                    y: 0,
                    speed: .1
                };
                var a = 1.8;
                //Ellipse width
                var b = .5;
                //Ellipse height
    
               //Scale and Pan variables
                var translatePos = {
                    x: 1,
                    y: 1
                };
                var startDragOffset = {};
                var mouseDown = false;
    
                var elements = [{}];
    
                // Animate
                var animateInterval = setInterval(drawScreen, 1);
    
                //Animation
                function drawScreen() {
                    context.clearRect(0, 0, cw, ch);
                    // Background box
                    context.beginPath();
                    context.fillStyle = '#EEEEEE';
                    context.fillRect(0, 0, theCanvas.width, theCanvas.height);
                    context.strokeRect(1, 1, theCanvas.width - 2, theCanvas.height - 2);
                    context.closePath();
    
                    context.save();
                    context.translate(panX, panY);
                    context.scale(scaleFactor, scaleFactor);
    
                    ball.speed = ball.speed + 0.001;
    
                    for (var i = 1; i <= no; i++) {
                        // male
                        new_angle = div_angle * i;
                        //Starting positions for ball 1 at different points on the ellipse
                        circle.angle = (new_angle * (0.0174532925)) + ball.speed;
                        //elliptical x position and y position for animation for the first ball
                        //xx and yy records the first balls coordinates
                        xx = ball.x = circle.centerX - (a * Math.cos(circle.angle)) * (circle.radius);
                        yy = ball.y = circle.centerY + (b * Math.sin(circle.angle)) * (circle.radius);
                        //Draw the first ball with position x and y
                        context.fillStyle = "#000000";
                        context.beginPath();
                        context.arc(ball.x, ball.y, 10, 0, Math.PI * 2, true);
                        context.fill();
                        context.closePath();
    
                        //alert("male Positions "+"X:  "+ball.x+ " Y: "+ball.y);
    
                        // female
                        new_angle = div_angle * i + 4;
                        //Starting positions for ball 2 at different points on the ellipse
                        circle.angle = (new_angle * (0.0174532925)) + ball.speed;
                        //elliptical x position and y position for animation for the second ball
                        //ball.x and ball.y record the second balls positions
                        ball.x = circle.centerX - (a * Math.cos(circle.angle)) * (circle.radius);
                        ball.y = circle.centerY + (b * Math.sin(circle.angle)) * (circle.radius);
                        context.fillStyle = "#000000";
                        context.beginPath();
                        context.arc(ball.x, ball.y, 10, 0, Math.PI * 2, true);
                        context.fill();
                        context.closePath();
    
                        //alert("female Positions "+"X:  "+ball.x+ " Y: "+ball.y);
    
                        //Record the ball positions in elements array for locating positions with mouse coordinates.
                        elements[i] = {
                            id: i,
                            femaleX: ball.x,
                            femaleY: ball.y,
                            maleX: xx,
                            maleY: yy,
                            w: 10 //radius of the ball to draw while locating the positions
                        };
                        //Text Numbering
                        context.beginPath();
                        context.fillStyle = "blue";
                        context.font = "bold 16px Arial";
                        context.fillText(elements[i].id, ball.x - 20, ball.y + 20);
                        context.closePath();
                        // line drawing--Connecting lines to the balls from the center.
                        context.moveTo(mainX, mainY);
                        context.lineTo((ball.x + xx) / 2, (ball.y + yy) / 2);
                        //Draw line till the middle point between ball1 and ball2
                        context.stroke();
                        context.fill();
                        context.closePath();
                    }
                    // center point
                    context.fillStyle = "#000000";
                    context.beginPath();
                    context.arc(mainX, mainY, 15, 0, Math.PI * 2, true);
                    context.fill();
                    context.closePath();
    
                    context.restore();
                }
    
                // Event Listeners
                // Mouse move event to alert the position of the ball on screen
    
    
                document.getElementById("plus").addEventListener("click", function () {
                    scaleFactor *= 1.1;
                    drawScreen();
                }, false);
    
                document.getElementById("minus").addEventListener("click", function () {
                    scaleFactor /= 1.1;
                    drawScreen();
                }, false);
    
                // Event listeners to handle screen panning
                context.canvas.addEventListener("mousedown", function (evt) {
                    mouseDown = true;
                    startDragOffset.x = evt.clientX - translatePos.x;
                    startDragOffset.y = evt.clientY - translatePos.y;
                });
    
                context.canvas.addEventListener("mouseup", function (evt) {
                    mouseDown = false;
                });
    
                context.canvas.addEventListener("mouseover", function (evt) {
                    mouseDown = false;
                });
    
                context.canvas.addEventListener("mouseout", function (evt) {
                    mouseDown = false;
                });
    
                context.canvas.addEventListener("mousemove", function (evt) {
                    if (mouseDown) {
                        translatePos.x = evt.clientX - startDragOffset.x;
                        translatePos.y = evt.clientY - startDragOffset.y;
    
                        panX = translatePos.x;
                        panY = translatePos.y;
    
                        drawScreen();
                    }
    
                    evt.preventDefault();
                    evt.stopPropagation();
    
                    var mouseX = parseInt(evt.clientX - offsetX);
                    var mouseY = parseInt(evt.clientY - offsetY);
    
                    var mouseXT = parseInt((mouseX - panX) / scaleFactor);
                    var mouseYT = parseInt((mouseY - panY) / scaleFactor);
    
                    status.innerHTML = mouseXT + " | " + mouseYT;
    
                    for (var i = 1; i < elements.length; i++) {
                        var b = elements[i];
                        context.closePath();
                        context.beginPath();
                        context.arc(b.femaleX, b.femaleY, 10, 0, Math.PI * 2);
                        context.arc(b.maleX, b.maleY, 10, 0, Math.PI * 2);
    
                        if (context.isPointInPath(mouseXT, mouseYT)) {
                            theCanvas.style.cursor = 'pointer';
                            alert(b.id + " female.x: " + b.femaleX + " female.y: " + b.femaleY + " ball.x: " + ball.x + " ball.y: " + ball.y);
                            return;
                        } else theCanvas.style.cursor = 'default';
                        context.closePath();
                    }
    
                });`
    

    解决方案

    Using the transformation matrix is useful or even necessary in these circumstances:

    • If you are deeply nesting transformations.
    • If you are altering different drawings with different transforms.
    • If you need interim transformation coordinates.
    • If you are doing transformations involving skew.
    • If you are doing transformations involving rotation.

    But for the simpler case of panning and scaling the entire canvas there is a simpler method.

    First, set up variables to hold the current amount of scaling and panning:

    var scaleFactor=1.00;
    var panX=0;
    var panY=0;
    

    Then use these pan & scale variables to do all your drawings.

    • clear the canvas.
    • save the untransformed canvas state.
    • do translations with the panX variable.
    • do scaling with the scaleFactor variable.
    • draw all your elements as if they were in untranformed space.
    • restore the context to its untransformed state.

    Example code:

    function drawTranslated(){
    
        ctx.clearRect(0,0,cw,ch);
    
        ctx.save();
        ctx.translate(panX,panY);
        ctx.scale(scaleFactor,scaleFactor);
    
        ctx.beginPath();
        ctx.arc(circleX,circleY,15,0,Math.PI*2);
        ctx.closePath();
        ctx.fillStyle=randomColor();
        ctx.fill();
    
        ctx.restore();
    
    }
    

    Now, about mouse coordinates:

    The browser always returns the mouse position in untransformed coordinates. Your drawings have been done in transformed space. If you want to know where your mouse is in transformed space, you can convert untransformed mouse coordinates to transformed coordinates like this:

    var mouseXTransformed = (mouseX-panX) / scaleFactor;
    var mouseYTransformed = (mouseY-panY) / scaleFactor;
    

    Here is example code and a Demo: http://jsfiddle.net/m1erickson/HwNp3/

    <!doctype html>
    <html>
    <head>
    <link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
    <script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
    <style>
        body{ background-color: ivory; }
        #canvas{border:1px solid red;}
    </style>
    <script>
    $(function(){
    
        var canvas=document.getElementById("canvas");
        var ctx=canvas.getContext("2d");
        var $canvas=$("#canvas");
        var canvasOffset=$canvas.offset();
        var offsetX=canvasOffset.left;
        var offsetY=canvasOffset.top;
        var scrollX=$canvas.scrollLeft();
        var scrollY=$canvas.scrollTop();
        var cw=canvas.width;
        var ch=canvas.height;
    
        var scaleFactor=1.00;
        var panX=0;
        var panY=0;
    
        var circleX=150;
        var circleY=150;
    
        var $screen=$("#screen");
        var $transformed=$("#transformed");
        var $trx=$("#trx");
    
        drawTranslated();
    
        $("#canvas").mousemove(function(e){handleMouseMove(e);});
        $("#scaledown").click(function(){ scaleFactor/=1.1; drawTranslated(); });
        $("#scaleup").click(function(){ scaleFactor*=1.1; drawTranslated(); });
        $("#panleft").click(function(){ panX-=10; drawTranslated(); });
        $("#panright").click(function(){ panX+=10; drawTranslated(); });
    
    
        function drawTranslated(){
            ctx.clearRect(0,0,cw,ch);
    
            ctx.save();
            ctx.translate(panX,panY);
            ctx.scale(scaleFactor,scaleFactor);
    
            ctx.beginPath();
            ctx.arc(circleX,circleY,15,0,Math.PI*2);
            ctx.closePath();
            ctx.fillStyle=randomColor();
            ctx.fill();
    
            ctx.restore();
    
            $trx.text("Pan: "+panX+", Scale: "+scaleFactor);
        }
    
        function handleMouseMove(e){
            e.preventDefault();
            e.stopPropagation();
    
            var mouseX=parseInt(e.clientX-offsetX);
            var mouseY=parseInt(e.clientY-offsetY);
    
            var mouseXT=parseInt((mouseX-panX)/scaleFactor);
            var mouseYT=parseInt((mouseY-panY)/scaleFactor);
    
            $screen.text("Screen Coordinates: "+mouseX+"/"+mouseY);
    
            $transformed.text("Transformed Coordinates: "+mouseXT+"/"+mouseYT);
        }
    
        function randomColor(){ 
            return('#'+Math.floor(Math.random()*16777215).toString(16));
        }
    
    }); // end $(function(){});
    </script>
    </head>
    <body>
        <h3>Transformed coordinates are mouseXY in transformed space.<br>The circles center is always at translated [150,150]</h3>
        <h4 id=screen>Screen Coordinates:</h4>
        <h4 id=transformed>Transformed Coordinates:</h4>
        <h4 id=trx>Pan & Scale</h4>
        <button id=scaledown>Scale Down</button>
        <button id=scaleup>Scale Up</button>
        <button id=panleft>Pan Left</button>
        <button id=panright>Pan Right</button><br>
        <canvas id="canvas" width=350 height=400></canvas>
    </body>
    </html>
    

    这篇关于缩放和平移画布后,鼠标坐标不匹配的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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