Click in Canvas is three pixels off
Asked Answered
B

1

4

I've spent the whole day trying to get a click over my canvas to return the pixel xy offset. What a mission this has been!

This is what I've ended up with:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>JS Bin</title>
</head>
<body>
 <script src="https://code.jquery.com/jquery-1.10.2.js"></script>

  <div id="logX">x</div>
  <div id="logY">y</div>

  <div style="margin-left:100px">
    <div style="margin-left:100px">
      <canvas id="myCanvas" width="100" height="1000" style="border:20px solid #000000;"></canvas>
    </div>
  </div>

  <script>
  var canvas = document.getElementById('myCanvas');
  canvas.addEventListener('click', on_canvas_click, false);

  function getNumericStyleProperty(style, prop) {
    return parseInt(style.getPropertyValue(prop),10);
  }

  function on_canvas_click(ev) {
    var boundingRect = ev.target.getBoundingClientRect();

    var x = ev.clientX - boundingRect.left,
        y = ev.clientY - boundingRect.top;

    var style = getComputedStyle(canvas, null);

    x -= getNumericStyleProperty(style, "margin-left");
    y -= getNumericStyleProperty(style, "margin-top");

    x -= getNumericStyleProperty(style, "border-left-width");
    y -= getNumericStyleProperty(style, "border-top-width");

    x -= getNumericStyleProperty(style, "padding-left");
    y -= getNumericStyleProperty(style, "padding-top");

    $("#logX").text(        ev.target.getBoundingClientRect().left
                   + ", " + ev.clientX
                   + ", " + canvas.offsetLeft
                   + ", " + x
                  );

    $("#logY").text(        ev.target.getBoundingClientRect().top
                   + ", " + ev.clientY
                   + ", " + canvas.offsetTop
                   + ", " + y
                  );
  }

  //$( document ).on( "mousemove", function( event ) {
  //$( "#log" ).text( "pageX: " + event.pageX + ", pageY: " + event.pageY );
  //});
</script>
</body>
</html>

http://jsbin.com/xajeluxija/2/

It produces a white canvas within a thick black border.

Click within the canvas and it will display the XY coordinates.

As you can see, I'm deliberately creating a canvas that requires scrolling, and is not aligned to the left. This is to force a robust solution. (Can the test case be improved?)

It very nearly works! But if you try clicking in the top left corner you will get (1,2).

It should be (0,0).

What is going wrong?

EDIT: getting mouse position relative to content area of an element -- this question has an excellent answer (together with live example) which still exhibits the same offset problem.

How do I get the coordinates of a mouse click on a canvas element? <-- this question is hopelessly cluttered.

http://miloq.blogspot.in/2011/05/coordinates-mouse-click-canvas.html <-- also exhibits the same behaviour.

Getting cursor position in a canvas without jQuery <-- uses document.documentElement which might be an alternative to faffling with CSS margin/border/padding(?)

EDIT: Now it is 2,2 not 2,1! It is inconsistent! ARGH! I took photos with my camera: enter image description here

EDIT: On Firefox I get 0.75, 1.91667... enter image description here

EDIT 15Apr: Two attempts here:
http://jsfiddle.net/Skz8g/47/
http://jsbin.com/taceso/1/

Borek answered 13/4, 2015 at 14:30 Comment(3)
i see a (0,0) => ?? Otherwise my code for mouse is here it handles css scaling : #20061191Attach
OK, run the answer's snippet in Chrome. Magnify the screen to about 400% using Mousewheel-up. This will let you target the top left pixel in canvas much easier.Sirotek
test with another mouse or clean it or check your mouse settings. On the screenshot the mouse makes a 'big' jump.Attach
S
4

As you've discovered, border size is counted in calculating mouse position.

So wrap your canvas in a container div with the container div having the 20px border.

This takes away the extra calculations needed if the border were on the canvas itself.

Note: I put the styles for #borderDiv & #myCanvas in a Style section in the Header.

var canvas = document.getElementById('myCanvas');
canvas.addEventListener('click', on_canvas_click, false);

var context=canvas.getContext('2d');

context.fillStyle='red';
context.fillRect(0,0,10,1);
context.fillRect(0,0,1,10);

function getNumericStyleProperty(style, prop) {
  return parseInt(style.getPropertyValue(prop),10);
}

function on_canvas_click(ev) {
  var boundingRect = ev.target.getBoundingClientRect();
  var x = ev.clientX - boundingRect.left,
      y = ev.clientY - boundingRect.top;

  $("#logX").text("x="+x);
  $("#logY").text("y="+y);
}
#borderDiv{margin:0px; width:100px; height:1000px; border:20px solid black;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<div id="logX">x</div>
<div id="logY">y</div>

<div style="margin-left:100px">
  <div style="margin-left:100px">
    <div id='borderDiv'>
      <canvas id="myCanvas" width=100 height=1000 style="cursor:crosshair"></canvas>
    </div>
  </div>
</div>
Sirotek answered 13/4, 2015 at 15:26 Comment(10)
Thanks, I'm still getting (1,2) though. I'm on OSX/Chrome. Is anyone able to corroborate this result? I am moving my cursor right to the top left of the inner rectangle, so that if I move it fractionally further left or up it will spill over into the black.Borek
My example code successfully reports 0,0 in Chrome Version 41.0.2272.118 m. I must admit trying 2-3 times to get the mouse over position 0,0 because 0,0 is only 1 pixel in size and it's awkward to hit. You might draw a red rectangle at 0,0 as a target :-)Sirotek
i also saw (0,0). Maybe your mouse settings are too fast => not precise enough ?Attach
You might draw red lines at 0,0 running down and right as a target. Also, an "oops" on my part. I meant to apply the canvas's width=100px and height=1000px in the html element rather than in the css. See my edited code as my oops will make a difference.Sirotek
I've added screenshots the original question using your snippet. Does this match what you guys are getting?Borek
Yes, that looks like what I get. Notice that in the second image, the mouse looks like it's over [2,2] or [2,3]. Again, a very hard target to hit. If you use my current snippet and magnify it, you should see two 1 pixel wide lines starting at [0,0] and extending rightward and downward. That way you have a mouse target to hit--although the lines themselves kind of show that there is a true [0,0] on your canvas. ;-)Sirotek
OK, run the snippet in Chrome. Magnify the screen to about 400% using Mousewheel-up. This will let you target the top left pixel in canvas much easier. Cheers!Sirotek
I just experimented on Firefox and got similar-ish (although noninteger) values. I can't imagine any situation where someone is going to require this kind of precision. Also I shouldn't assume that the pixel behind the visible tip of my cursor is the same as what the cursor thinks it is looking at. I'm going to mentally file it under Heisenberg's uncertainty principle and move on. Thanks a ton for digging into this!Borek
Glad I could help! You can parseInt the mouse coordinates for a more consistent "sane" set of coordinates. :-)Sirotek
Thanks to brainjam's suggestion here I modified your snippet by adding a style="cursor:crosshair" attribute to <canvas>, which shows clearly that it is indeed a problem with the cursor glyph.Borek

© 2022 - 2024 — McMap. All rights reserved.