画布中鼠标的实际位置

163
我试图在HTML5画布上使用鼠标绘图,但似乎唯一能正常工作的方法是将画布放置在位置0,0(左上角),如果我更改画布位置,由于某种原因它无法像应该那样绘制。以下是我的代码。
 function createImageOnCanvas(imageId){
    document.getElementById("imgCanvas").style.display = "block";
    document.getElementById("images").style.overflowY= "hidden";
    var canvas = document.getElementById("imgCanvas");
    var context = canvas.getContext("2d");
    var img = new Image(300,300);
    img.src = document.getElementById(imageId).src;
    context.drawImage(img, (0),(0));
}

function draw(e){
    var canvas = document.getElementById("imgCanvas");
    var context = canvas.getContext("2d");
    posx = e.clientX;
    posy = e.clientY;
    context.fillStyle = "#000000";
    context.fillRect (posx, posy, 4, 4);
}

HTML部分

 <body>
 <div id="images">
 </div>
 <canvas onmousemove="draw(event)" style="margin:0;padding:0;" id="imgCanvas"
          class="canvasView" width="250" height="250"></canvas> 

我读到过在JavaScript中创建一个简单的函数来获取正确位置的方法,但我不知道如何做到。

5个回答

331

简单的1:1场景

对于画布元素与位图大小相等的情况,您可以使用以下代码片段来获取鼠标位置:

function getMousePos(canvas, evt) {
  var rect = canvas.getBoundingClientRect();
  return {
    x: evt.clientX - rect.left,
    y: evt.clientY - rect.top
  };
}

只需在事件中使用事件和画布作为参数调用它。它返回一个带有鼠标位置 xy 的对象。

由于您获取的鼠标位置是相对于客户端窗口的,因此您需要减去画布元素的位置以将其转换为相对于元素本身的位置。

以下是在您的代码中集成的示例:

// put this outside the event loop..
var canvas = document.getElementById("imgCanvas");
var context = canvas.getContext("2d");

function draw(evt) {
  var pos = getMousePos(canvas, evt);

  context.fillStyle = "#000000";
  context.fillRect (pos.x, pos.y, 4, 4);
}

注意:如果直接应用边框和填充到画布元素,则会影响位置,因此需要通过getComputedStyle()考虑这些问题 - 或者将这些样式应用于父div。

当元素和位图大小不同时

当元素的大小与位图本身不同时,例如使用CSS缩放元素或存在像素宽高比等情况时,您需要解决此问题。

例如:

function  getMousePos(canvas, evt) {
  var rect = canvas.getBoundingClientRect(), // abs. size of element
    scaleX = canvas.width / rect.width,    // relationship bitmap vs. element for x
    scaleY = canvas.height / rect.height;  // relationship bitmap vs. element for y

  return {
    x: (evt.clientX - rect.left) * scaleX,   // scale mouse coordinates after they have
    y: (evt.clientY - rect.top) * scaleY     // been adjusted to be relative to element
  }
}

应用于上下文的变换(缩放、旋转等)

然后就有了更复杂的情况,即您已经对上下文应用了变换,例如旋转、倾斜/剪切、缩放、平移等。为了处理这种情况,您可以计算当前矩阵的逆矩阵。

较新的浏览器允许您通过 currentTransform 属性读取当前矩阵,甚至 Firefox(当前 alpha 版本)还通过 mozCurrentTransformInverted 提供了一个反转矩阵。但是,Firefox 通过 mozCurrentTransform 将返回一个数组,而不是应该返回的 DOMMatrix。启用实验性标志后,Chrome 也不会返回 DOMMatrix,而是返回 SVGMatrix

然而,在大多数情况下,您将不得不实现自己的自定义矩阵解决方案(例如我的解决方案 here - 免费/MIT 项目),直到它得到完全支持。

无论您采取哪种方法获得矩阵,最终都需要获取矩阵并将其反转并应用于鼠标坐标。然后将坐标传递给画布,画布将使用其矩阵将其转换回当前位置。

这样,点就会相对于鼠标处于正确的位置。此外,在应用逆矩阵之前,您需要调整坐标以相对于元素。

以下是一个仅展示矩阵步骤的示例:

function draw(evt) {
  var pos = getMousePos(canvas, evt);        // get adjusted coordinates as above
  var imatrix = matrix.inverse();            // get inverted matrix somehow
  pos = imatrix.applyToPoint(pos.x, pos.y);  // apply to adjusted coordinate
    
  context.fillStyle = "#000000";
  context.fillRect(pos.x-1, pos.y-1, 2, 2);
}

当实现时,使用currentTransform的示例如下:

  var pos = getMousePos(canvas, e);          // get adjusted coordinates as above
  var matrix = ctx.currentTransform;         // W3C (future)
  var imatrix = matrix.invertSelf();         // invert

  // apply to point:
  var x = pos.x * imatrix.a + pos.y * imatrix.c + imatrix.e;
  var y = pos.x * imatrix.b + pos.y * imatrix.d + imatrix.f;

更新:我创建了一个免费的解决方案(MIT),将所有这些步骤嵌入到一个易于使用的对象中,可以在 这里找到,并处理大多数忽略的其他琐碎的事情。


3
getBoundingRectangle() 提供比 canvas.getOffset() 更精确的坐标。 - poorva
嗯,这些坐标不完美。在手机上我得到了错误的值。有人知道如何修复或者在哪里查找吗? - Bitterblue
2
我更新了答案以解决伸缩问题。 - user1693593
我处于1:1的情况,并使用以下代码: var bRect = theCanvas.getBoundingClientRect(); mouseX = (evt.clientX - bRect.left)*(theCanvas.width/bRect.width); mouseY = (evt.clientY - bRect.top)*(theCanvas.height/bRect.height); 在所有浏览器中都可以运行,包括Safari iOS。但在Chrome Android设备中无法正常工作。可能出了什么问题? - Adam
4
这篇文章对我非常有帮助。我创建了一个 JSFiddle,将所有内容汇总在一起:https://jsfiddle.net/mattdeeds/yqLvza57/37/ - MattD
显示剩余11条评论

65

你可以使用这段代码来获取鼠标位置:

function getMousePos(canvas, evt) {
    var rect = canvas.getBoundingClientRect();
    return {
        x: (evt.clientX - rect.left) / (rect.right - rect.left) * canvas.width,
        y: (evt.clientY - rect.top) / (rect.bottom - rect.top) * canvas.height
    };
}

该代码考虑到了将坐标转换为画布空间(evt.clientX - rect.left)以及在画布逻辑尺寸与其样式尺寸不同时进行缩放(/ (rect.right - rect.left) * canvas.width,参见:HTML5中的Canvas宽度和高度)。

示例:http://jsfiddle.net/sierawski/4xezb7nL/

来源:jerryj在http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/上的评论。


我必须使用这个版本,因为在缩放和/或调整窗口大小时,我的画布尺寸发生了变化。我已经通过使用getComputedStyle()计算出的宽度和高度比例进行缩放来解决了这个问题,但是这个解决方案更加优雅。我会推荐这个答案给那些想要更通用解决方案的人。 - osolmaz
1
它假设画布上没有边框(如果有,那么会很麻烦,最好不要在画布上放置边框)。 - gman
@Rafal S:我使用以下代码:var bRect = theCanvas.getBoundingClientRect(); mouseX = (evt.clientX - bRect.left)*(theCanvas.width/bRect.width); mouseY = (evt.clientY - bRect.top)*(theCanvas.height/bRect.height);在PC上的Chrome浏览器、iOS上的Safari浏览器中都可以正常工作,但在Android上的Chrome浏览器中,mouseY的值偏差很大。请问您能否提供修复代码? - Adam
1
这是一个很好的解决方案,但我想我可以提供一个关于使用画布滚动页面的解决方案。这种方法可能并不适用于所有情况,但它在我的项目中起作用了: y: ((event.pageY - rect.top) / (rect.bottom - rect.top) * canvas.height) - window.scrollY - Forseth11
非常感谢您。 - Syed Arshad
由于某种原因,这个比我之前做的要快很多。谢谢! - Daniel me

9

您需要获取鼠标相对于画布的位置。

为此,您需要知道页面上的画布的X/Y位置。

这被称为画布的“偏移量”,以下是获取偏移量的方法。(我使用jQuery来简化跨浏览器兼容性,但如果您想使用原始javascript,可以通过快速搜索获得)。

    var canvasOffset=$("#canvas").offset();
    var offsetX=canvasOffset.left;
    var offsetY=canvasOffset.top;

然后在你的鼠标处理程序中,你可以像这样获取鼠标的X/Y坐标:

  function handleMouseDown(e){
      mouseX=parseInt(e.clientX-offsetX);
      mouseY=parseInt(e.clientY-offsetY);
}

这里有一段代码和示例,展示了如何成功地在画布上跟踪鼠标事件:

http://jsfiddle.net/m1erickson/WB7Zu/

<!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 canvasOffset=$("#canvas").offset();
    var offsetX=canvasOffset.left;
    var offsetY=canvasOffset.top;

    function handleMouseDown(e){
      mouseX=parseInt(e.clientX-offsetX);
      mouseY=parseInt(e.clientY-offsetY);
      $("#downlog").html("Down: "+ mouseX + " / " + mouseY);

      // Put your mousedown stuff here

    }

    function handleMouseUp(e){
      mouseX=parseInt(e.clientX-offsetX);
      mouseY=parseInt(e.clientY-offsetY);
      $("#uplog").html("Up: "+ mouseX + " / " + mouseY);

      // Put your mouseup stuff here
    }

    function handleMouseOut(e){
      mouseX=parseInt(e.clientX-offsetX);
      mouseY=parseInt(e.clientY-offsetY);
      $("#outlog").html("Out: "+ mouseX + " / " + mouseY);

      // Put your mouseOut stuff here
    }

    function handleMouseMove(e){
      mouseX=parseInt(e.clientX-offsetX);
      mouseY=parseInt(e.clientY-offsetY);
      $("#movelog").html("Move: "+ mouseX + " / " + mouseY);

      // Put your mousemove stuff here

    }

    $("#canvas").mousedown(function(e){handleMouseDown(e);});
    $("#canvas").mousemove(function(e){handleMouseMove(e);});
    $("#canvas").mouseup(function(e){handleMouseUp(e);});
    $("#canvas").mouseout(function(e){handleMouseOut(e);});

}); // end $(function(){});
</script>

</head>

<body>
    <p>Move, press and release the mouse</p>
    <p id="downlog">Down</p>
    <p id="movelog">Move</p>
    <p id="uplog">Up</p>
    <p id="outlog">Out</p>
    <canvas id="canvas" width=300 height=300></canvas>

</body>
</html>

我曾经使用了$("#canvas").offsetLeft来代替$("#canvas").offset(),结果相对位置不正确! - poorva

6
在画布事件中计算正确的鼠标点击或鼠标移动位置的最简单方法是使用以下等式:
canvas.addEventListener('click', event =>
{
    let bound = canvas.getBoundingClientRect();

    let x = event.clientX - bound.left - canvas.clientLeft;
    let y = event.clientY - bound.top - canvas.clientTop;

    context.fillRect(x, y, 16, 16);
});

如果画布有左侧填充(padding-left)或上方填充(padding-top),则通过以下方式减去x和y值:
``` x -= parseFloat(style['padding-left'].replace('px')); y -= parseFloat(style['padding-top'].replace('px')); ```

3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接