缩放和平移画布后鼠标坐标不匹配
Posted
技术标签:
【中文标题】缩放和平移画布后鼠标坐标不匹配【英文标题】:Mouse coordinates don't match after Scaling and Panning canvas 【发布时间】:2014-09-13 01:11:31 【问题描述】:我对 javascript 和 canvas 非常陌生,我有一个程序可以检测椭圆路径上的动画元素。它稍后会形成一棵树。但这是我链接到 jsfiddle 的基本结构。 它可以在没有缩放或平移的情况下正常工作,但是一旦我尝试缩放或平移,鼠标坐标就会变得混乱。 我试着听从 html5 canvas get coordinates after zoom and translate 的 markE 的建议 但我肯定做错了什么,我显然不明白画布和转换矩阵发生了什么。我花了大约 3 天的时间试图改变我能想到的所有组合,但我似乎无法弄清楚:s
已解决: 这是我的代码,其中包含缩放和鼠标平移以及用于动画和检测椭圆上的元素: 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();
);`
【问题讨论】:
您的代码太大了。首先是将它分解成一小部分,您可以简单地进行单元测试。例如,有一个单独的部分用于矩阵处理并检查它是否正常工作。编写一些小绘图函数来简化(drawBackground,drawBall,...)并从代码中取消事件处理的相关性(您的 mousemove 就像 60 行长!!)。借助经过测试的小型构建块,您很快就会发现问题所在。 哈哈!我发呆了很长一段时间,试图让它作为一个整体工作。但我确实将其分解为基础知识。它确实得到了解决。谢谢你的建议:)我真的不知道为什么我以前没有想过这样做。 看不到复杂性已达到“关键”阈值是一个常见的陷阱,不用担心。当我遇到一个错误时,我总是想知道它只是一个错误还是我需要重构我的代码的迹象。顺便说一句,我在您发布的编辑中没有看到太大变化,举一个重构示例,我在这个小提琴jsfiddle.net/gamealchemist/A8hgz/2 中对 drawScreen() 进行了(部分)重构 【参考方案1】:在这些情况下使用变换矩阵是有用的,甚至是必要的:
如果您正在深度嵌套转换。 如果您使用不同的变换更改不同的图纸。 如果您需要临时变换坐标。 如果您正在进行涉及倾斜的转换。 如果您正在进行涉及旋转的变换。但是对于平移和缩放整个画布的更简单的情况,有一个更简单的方法。
首先,设置变量来保存当前的缩放和平移量:
var scaleFactor=1.00;
var panX=0;
var panY=0;
然后使用这些平移和缩放变量来绘制所有绘图。
清除画布。 保存未转换的画布状态。 使用panX
变量进行翻译。
使用scaleFactor
变量进行缩放。
绘制所有元素,就好像它们在未转换的空间中一样。
将上下文恢复到未转换的状态。
示例代码:
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();
现在,关于鼠标坐标:
浏览器总是以未转换的坐标返回鼠标位置。您的绘画是在变形空间中完成的。如果您想知道鼠标在变换空间中的位置,可以将未变换的鼠标坐标转换为变换的坐标,如下所示:
var mouseXTransformed = (mouseX-panX) / scaleFactor;
var mouseYTransformed = (mouseY-panY) / scaleFactor;
这是示例代码和演示: 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;
#canvasborder: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>
【讨论】:
非常感谢。解决了我的问题!以上是关于缩放和平移画布后鼠标坐标不匹配的主要内容,如果未能解决你的问题,请参考以下文章