3
votes

I'm trying to work with an HTML5 canvas element. One thing i'm trying to do is set up a mousemove event to track the mouse on the canvas for drawing and other purposes. I have not been able to find a definitive answer on how to get the exact coordinates of the pixel in the canvas the mouse is over. I found a tutorial on the web that had this html (I added the background-color to the canvas to make it obvious on the rendered page):

<!DOCTYPE HTML>
<html>
  <head>
    <style>
      body {
        margin: 0px;
        padding: 0px;
      }

      #myCanvas {
        background-color: cornflowerblue;
      }
    </style>
  </head>
  <body>
    <canvas id="myCanvas" width="578" height="200"></canvas>
    <script>
      function writeMessage(canvas, message) {
        var context = canvas.getContext('2d');
        context.clearRect(0, 0, canvas.width, canvas.height);
        context.font = '18pt Calibri';
        context.fillStyle = 'black';
        context.fillText(message, 10, 25);
      }
      function getMousePos(canvas, evt) {
        var rect = canvas.getBoundingClientRect();
        return {
          x: evt.clientX - rect.left,
          y: evt.clientY - rect.top
        };
      }
      var canvas = document.getElementById('myCanvas');
      var context = canvas.getContext('2d');

      canvas.addEventListener('mousemove', function(evt) {
        var mousePos = getMousePos(canvas, evt);
        var message = 'Mouse position: ' + mousePos.x + ',' + mousePos.y;
        writeMessage(canvas, message);
      }, false);
    </script>
  </body>
</html>

When the page is loaded, you see the canvas as a blueish rectangle in the upper left corner of the page. Move the mouse over it and the text in the canvas changes to show the mouse position as two ints, one each for X and Y.

I then modified the canvas style by adding a top and left margin, and left the rest of the page unchanged.

#myCanvas {
  background-color: cornflowerblue;
  margin-left: 30px;
  margin-top: 30px;
} 

When I rendered the page now, the blue rectangle of the canvas was offset from the top and left of the page as I expected. But passing my mouse over the canvas now had the X and Y mouse coordinates that were being displayed in the canvas coming up as floats with many decimal places. Tracing through the code a bit, it seems that getBoundingClientRect() is returning a rect where the values top and left are floats.

I assume I could do something like truncate or round the values being returned by getBoundingClientRect(), but that feels like the wrong way to go about it to me.

Am i somehow using getBoundingClientRect() incorrectly, or is it expected that it should return float values?

And is there a clear cut way to get the exact X and Y coordinates of the mouse over the canvas when listening for various mouse events?

1

1 Answers

8
votes

tldr; you did zoom/unzoom in your browser.

The problem

margin-left: 30px;

In this jsfiddle thats mimics your problem it does not works as you said on my computer when navigator zoom is set to 100% (normal). But if you zoom inside your browser you will notice such behavior.

margin-left: 11%;

Instead if you use % margin like in this jsfiddle you will notice it does returns floating mouse position wether zoom is on or not.

The answer

The thing is mouse position is computed as it appears on the screen : it may only have entire position coordinates since it is pixel based.

However getBoundingClientRect returns what browser computes to be "Bounding Client Rect of the element" after applying margins, zoom and others modifiers but before telling the GPU to render it. In short it returns the real position of the element which is later approximated by the GPU to be rendered within a matrix of pixels. If you use pixel margins/sizes/paddings/etc then elements positions remains integer based, but if you zoom or use em/% positioning values then it may result floating positions.

The solutions

  1. round bounds
  2. assume it is indeed a floating position and it's just the GPU that needs to round it in order to make it fit on the screen

Edit : The forgotten solutions