【问题标题】:Real mouse position in canvas画布中的真实鼠标位置
【发布时间】:2013-06-12 09:30:22
【问题描述】:

我正在尝试用鼠标在 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 中创建一个简单的函数来获得正确的位置,但我不知道如何去做。

【问题讨论】:

    标签: javascript canvas html5-canvas mouse-position


    【解决方案1】:

    简单的 1:1 场景

    对于canvas元素与位图大小1:1的情况,可以使用这个sn-p获取鼠标位置:

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

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

    由于您获得的鼠标位置是相对于客户端窗口的,因此您必须减去画布元素的位置才能将其转换为相对于元素本身的位置。

    代码中的集成示例:

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

    当 Element 和 Bitmap 大小不同时

    当元素的大小与位图本身不同时,例如,使用 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),将所有这些步骤嵌入到一个易于使用的对象中,该对象可以在 here 找到,并且还处理了其他一些细节-坚韧不拔的事情最容易被忽视。

    【讨论】:

    • getBoundingRectangle() 提供比 canvas.getOffset() 更精确的坐标;
    • 嗯,这些坐标并不完美。我在手机上得到错误的值。有人知道解决方法或去哪里找吗?
    • @Bitterblue 用于修复检查我在here 中提供的少量修改。它考虑到画布大小可能与其样式大小不同,这可能是您的问题。
    • 我更新了解决扩展问题的答案。
    • 这篇文章对我很有帮助。我创建了一个 JSFiddle 将所有内容整合在一起:jsfiddle.net/mattdeeds/yqLvza57/37
    【解决方案2】:

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

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

    这被称为画布的“偏移量”,这里是如何获取偏移量。 (我使用 jQuery 是为了简化跨浏览器的兼容性,但如果您想使用原始 javascript,Google 也可以快速获得)。

        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").offset();然后左上角我使用的是 $("#canvas").offsetLeft ,所以相对位置不对!
    【解决方案3】:

    参考这个问题:The mouseEvent.offsetX I am getting is much larger than actual canvas size .我在那里给出了一个完全适合你情况的函数

    【讨论】:

      【解决方案4】:

      你可以使用这个sn-p获取鼠标位置:

      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 参见:Canvas width and height in HTML5)。

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

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

      【讨论】:

      • 我不得不使用这个版本,因为我的画布大小在缩放和/或调整窗口大小时发生了变化。我已经通过使用getComputedStyle() 对计算出的宽度和高度进行缩放解决了这个问题,但是这个解决方案更优雅。我会向那些想要更通用解决方案的人推荐这个答案。
      • 它假定画布上没有边框(如果有它是一个 PITA 所以最好不要在画布上放置边框)
      • @Rafal S :我在浏览器 chrome 中使用 var bRect = theCanvas.getBoundingClientRect(); mouseX = (evt.clientX - bRect.left)*(theCanvas.width/bRect.width); mouseY = (evt.clientY - bRect.top)*(theCanvas.height/bRect.height);,在 PC 中使用 IE,在 iOS 中使用 safari 还可以,但在 chrome web 中,android 是错误的 mouseY。我可以用你的代码修复它吗?
      • 这是一个很好的解决方案,但我只是想我可能会添加一个解决方案来使用画布滚动页面。这可能不适用于所有情况,但它适用于我的项目:y: ((event.pageY - rect.top) / (rect.bottom - rect.top) * canvas.height) - window.scrollY
      • 非常感谢。
      【解决方案5】:

      在画布事件上计算正确的鼠标点击或鼠标移动位置的最简单方法是使用这个小等式:

      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-leftpadding-top,则通过以下方式减去 x 和 y:

      x -= parseFloat(style['padding-left'].replace('px'));
      y -= parseFloat(style['padding-top'].replace('px'));

      【讨论】:

        猜你喜欢
        • 2014-01-20
        • 1970-01-01
        • 2015-02-12
        相关资源
        最近更新 更多