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/

Community
  • 1
  • 1
P i
  • 29,020
  • 36
  • 159
  • 267
  • 1
    i see a (0,0) => ?? Otherwise my code for mouse is here it handles css scaling : http://stackoverflow.com/questions/20060691/most-modern-method-of-getting-mouse-position-within-a-canvas-in-native-javascrip/20061533#20061533 – GameAlchemist Apr 13 '15 at 15:23
  • 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. – markE Apr 13 '15 at 16:01
  • test with another mouse or clean it or check your mouse settings. On the screenshot the mouse makes a 'big' jump. – GameAlchemist Apr 13 '15 at 16:04

1 Answers1

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>
P i
  • 29,020
  • 36
  • 159
  • 267
markE
  • 102,905
  • 11
  • 164
  • 176
  • 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. – P i Apr 13 '15 at 15:29
  • 1
    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 :-) – markE Apr 13 '15 at 15:34
  • i also saw (0,0). Maybe your mouse settings are too fast => not precise enough ? – GameAlchemist Apr 13 '15 at 15:39
  • 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. – markE Apr 13 '15 at 15:40
  • I've added screenshots the original question using your snippet. Does this match what you guys are getting? – P i Apr 13 '15 at 15:48
  • 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. ;-) – markE Apr 13 '15 at 15:53
  • 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! – markE Apr 13 '15 at 16:00
  • 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! – P i Apr 13 '15 at 16:14
  • Glad I could help! You can `parseInt` the mouse coordinates for a more consistent "sane" set of coordinates. :-) – markE Apr 13 '15 at 16:45
  • 1
    Thanks to brainjam's suggestion [here](http://stackoverflow.com/a/5776220/435129) I modified your snippet by adding a `style="cursor:crosshair"` attribute to ``, which shows clearly that it is indeed a problem with the cursor glyph. – P i Apr 13 '15 at 17:42