How do I get the coordinates of a mouse click on a canvas element? [duplicate]
Asked Answered
O

22

333

What's the simplest way to add a click event handler to a canvas element that will return the x and y coordinates of the click (relative to the canvas element)?

No legacy browser compatibility required, Safari, Opera and Firefox will do.

Orison answered 11/9, 2008 at 2:7 Comment(7)
This should not be any different from getting mouse events from normal dom elements. quirksmode has a good reference on that.Oglethorpe
The code you list above only works when the canvas isn't deep inside other containers. In general you need to use something like the jquery offset function [var testDiv = $('#testDiv'); var offset = testDiv.offset();] to get the correct offset in a cross browser way. This is a real pain in the ***.Copula
The code posted above with Update fails to work if the page containing the canvas scrolls.Aletheaalethia
I removed my old "answer" that was included as an update to the question. As mentioned, it was out of date and incomplete.Orison
event.layerX and event.layerY are now in Chrome, FF, and MSIE(9)Hemolysis
Since there is like 50 answers here, I recommend scrolling to this guys answer: patriques - a good and simple 5 liner.Protonema
This question has become horribly cluttered. More up-to-date links here: #29608424Orchidaceous
B
281

If you like simplicity but still want cross-browser functionality I found this solution worked best for me. This is a simplification of @Aldekein´s solution but without jQuery.

function getCursorPosition(canvas, event) {
    const rect = canvas.getBoundingClientRect()
    const x = event.clientX - rect.left
    const y = event.clientY - rect.top
    console.log("x: " + x + " y: " + y)
}

const canvas = document.querySelector('canvas')
canvas.addEventListener('mousedown', function(e) {
    getCursorPosition(canvas, e)
})
Browning answered 5/8, 2013 at 8:26 Comment(15)
If the page is scrolled down, I guess one should also take the document's scroll offset into consideration.Cleek
@PeppeL-G bounding client rectangle calculates that for you. You can easily test it in console before you post a comment (that's what I did).Preen
@TomášZato, oh, getBoundingClientRect returns positions relative to the view port? Then my guess was wrong. I never tested it out since this was never a problem for me, and I kindly wanted to warn other readers of a potentially problem I saw, but thank you for your clarification.Cleek
What did I do wrong? I use canvas.addEventListener('mousedown', getCursorPosition(canvas, this), false); to trigger this function and I get x: NaN y: N in the console besides it seems like it only fires once.Duly
And if I use document.getElementById('blah').onclick = function getCursorPosition(canvas, event), I will get canvas.getBoundingClientRect is not a function at HTMLCanvasElement.getCursorPosition...Duly
Okay I guess I figured it out, if I use document.getElementById('blah').onclick = I will simply remove the canvas and write function getCursorPosition(event)Duly
This doesn't work if the canvas is scaled. Not sure if that's a browser bug.Donoho
It should be mentioned that using getBoundingClientRect causes reflow, often a serious performance bottleneck. Using offsetX and offsetY in modern browsers is the only correct way to do it.Nugent
Add usage for someone like me: var canvas = document.getElementById('canvasID'); canvas.addEventListener("mousedown", function (e) { getCursorPosition(canvas, e);});Goeger
Unhelpful without usage: how to add the event listener; how to get the canvas element.Trometer
@Goeger Added your usage example to the answer. THANKSBrowning
@Browning My pleasure :)Goeger
May need to scale it: https://mcmap.net/q/100581/-real-mouse-position-in-canvasUnwelcome
This worked great for me, and even properly handled margins/padding (which other techniques choked on). Thanks for sharing!Ratchford
This doesn't work with scroll!!!!!Lialiabilities
A
181

Update (5/5/16): patriques' answer should be used instead, as it's both simpler and more reliable.


Since the canvas isn't always styled relative to the entire page, the canvas.offsetLeft/Top doesn't always return what you need. It will return the number of pixels it is offset relative to its offsetParent element, which can be something like a div element containing the canvas with a position: relative style applied. To account for this you need to loop through the chain of offsetParents, beginning with the canvas element itself. This code works perfectly for me, tested in Firefox and Safari but should work for all.

function relMouseCoords(event){
    var totalOffsetX = 0;
    var totalOffsetY = 0;
    var canvasX = 0;
    var canvasY = 0;
    var currentElement = this;

    do{
        totalOffsetX += currentElement.offsetLeft - currentElement.scrollLeft;
        totalOffsetY += currentElement.offsetTop - currentElement.scrollTop;
    }
    while(currentElement = currentElement.offsetParent)

    canvasX = event.pageX - totalOffsetX;
    canvasY = event.pageY - totalOffsetY;

    return {x:canvasX, y:canvasY}
}
HTMLCanvasElement.prototype.relMouseCoords = relMouseCoords;

The last line makes things convenient for getting the mouse coordinates relative to a canvas element. All that's needed to get the useful coordinates is

coords = canvas.relMouseCoords(event);
canvasX = coords.x;
canvasY = coords.y;
Amorete answered 9/5, 2011 at 3:29 Comment(16)
is this using the library prototype? Is there a way to do it without a library?Paraph
No, it is using the builtin javascript prototype object --- developer.mozilla.org/en/…Kulda
my Chrome has event.offsetX and event.offsetY attributes, so I modified your solution by adding if (event.offsetX !== undefined && event.offsetY !== undefined) { return {x:event.offsetX, y:event.offsetY}; }. looks like it works.Bergess
Baczek is correct about Chrome's event.offsetX and event.offsetY which also works in IE9. For Firefox (tested w/ v13) you can use event.layerX and event.layerY.Zurn
For this to work when the canvas is positioned on a scrollable container I had to add: totalOffsetX += currentElement.offsetLeft - currentElement.scrollLeft; totalOffsetY += currentElement.offsetTop - currentElement.scrollTop;Hindoo
@Hindoo You're right. Updated my answer to accomodate scrolling containers.Amorete
I additionally added this: canvasX = event.pageX - totalOffsetX - document.body.scrollLeft; canvasY = event.pageY - totalOffsetY - document.body.scrollTop;Hindoo
@Hindoo Is that necessary? Doesn't pageX/Y ignore the scrolling on the page?Amorete
It was necessary for my situation but I had nested scrollable elements. I haven't thought about it particularly hard though, just accepted my change once it worked :)Hindoo
To make it work stand-alone for any event rather than as a prototype method on Canvas, I changed var currentElement = this; to var currentElement = event.currentElement;Bemoan
I tested that the 'event.layerX' works for Chrome (version-27.0.1453.110 m) as well; not only FF.Entente
This final version on answer didn't work for me. On Firefox, if whole screen is scrolled, I got the displaced value as output. What worked for me was a very similar solution, given on https://mcmap.net/q/100582/-what-substitute-should-we-use-for-layerx-layery-since-they-are-deprecated-in-webkit that instead of event.pageX/Y, it subtracted calculated offset from event.clientX/Y. Could someone review this and explain?Orpheus
The method isn't very fast... Couldn't you cache something after first run?Preen
This worked for a simple page, but once I buried it in a complex page, it no longer worked. However, patriques' answer did work on the complex page: https://mcmap.net/q/98336/-how-do-i-get-the-coordinates-of-a-mouse-click-on-a-canvas-element-duplicateMadriene
I get an error ReferenceError: canvas is not definedPiled
I am geting an error ReferenceError: event not defined.Telescopic
S
82

Edit 2018: This answer is pretty old and it uses checks for old browsers that are not necessary anymore, as the clientX and clientY properties work in all current browsers. You might want to check out Patriques Answer for a simpler, more recent solution.

Original Answer:
As described in an article i found back then but exists no longer:

var x;
var y;
if (e.pageX || e.pageY) { 
  x = e.pageX;
  y = e.pageY;
}
else { 
  x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; 
  y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; 
} 
x -= gCanvasElement.offsetLeft;
y -= gCanvasElement.offsetTop;

Worked perfectly fine for me.

Salol answered 13/12, 2010 at 15:38 Comment(2)
This solution doesn't always work - Ryan Artecona's answer works in the more general case when the canvas is not necessarily positioned relative to the whole page.Production
This solution is not fitted if performance matters, since Dom access are done on each click / move. And getBoundingClientRect exists now, and is more elegant.Gillmore
Z
40

Modern browser's now handle this for you. Chrome, IE9, and Firefox support the offsetX/Y like this, passing in the event from the click handler.

function getRelativeCoords(event) {
    return { x: event.offsetX, y: event.offsetY };
}

Most modern browsers also support layerX/Y, however Chrome and IE use layerX/Y for the absolute offset of the click on the page including margin, padding, etc. In Firefox, layerX/Y and offsetX/Y are equivalent, but offset didn't previously exist. So, for compatibility with slightly older browsers, you can use:

function getRelativeCoords(event) {
    return { x: event.offsetX || event.layerX, y: event.offsetY || event.layerY };
}
Zurn answered 24/8, 2012 at 17:41 Comment(2)
interesting how layerX, layerY is defined on both Chrome and Firefox, but on Chrome it is inaccurate (or means something else).Photooffset
@JulianMann Thanks for the info. I've updated this answer based on more current support. Looks like you can get away with offsetX/Y almost universally now.Zurn
S
22

So this is both simple but a slightly more complicated topic than it seems.

First off there are usually to conflated questions here

  1. How to get element relative mouse coordinates

  2. How to get canvas pixel mouse coordinates for the 2D Canvas API or WebGL

so, answers

How to get element relative mouse coordinates

Whether or not the element is a canvas getting element relative mouse coordinates is the same for all elements.

There are 2 simple answers to the question "How to get canvas relative mouse coordinates"

Simple answer #1 use offsetX and offsetY

canvas.addEventListner('mousemove', (e) => {
  const x = e.offsetX;
  const y = e.offsetY;
});

This answer works in Chrome, Firefox, and Safari. Unlike all the other event values offsetX and offsetY take CSS transforms into account.

The biggest problem with offsetX and offsetY is as of 2019/05 they don't exist on touch events and so can't be used with iOS Safari. They do exist on Pointer Events which exist in Chrome and Firefox but not Safari although apparently Safari is working on it.

Another issue is the events must be on the canvas itself. If you put them on some other element or the window you can not later choose the canvas to be your point of reference.

Simple answer #2 use clientX, clientY and canvas.getBoundingClientRect

If you don't care about CSS transforms the next simplest answer is to call canvas. getBoundingClientRect() and subtract the left from clientX and top from clientY as in

canvas.addEventListener('mousemove', (e) => {
  const rect = canvas.getBoundingClientRect();
  const x = e.clientX - rect.left;
  const y = e.clientY - rect.top;
});

This will work as long as there are no CSS transforms. It also works with touch events and so will work with Safari iOS

canvas.addEventListener('touchmove', (e) => {
  const rect = canvas. getBoundingClientRect();
  const x = e.touches[0].clientX - rect.left;
  const y = e.touches[0].clientY - rect.top;
});

How to get canvas pixel mouse coordinates for the 2D Canvas API

For this we need to take the values we got above and convert from the size the canvas is displayed to the number of pixels in the canvas itself

with canvas.getBoundingClientRect and clientX and clientY

canvas.addEventListener('mousemove', (e) => {
  const rect = canvas.getBoundingClientRect();
  const elementRelativeX = e.clientX - rect.left;
  const elementRelativeY = e.clientY - rect.top;
  const canvasRelativeX = elementRelativeX * canvas.width / rect.width;
  const canvasRelativeY = elementRelativeY * canvas.height / rect.height;
});

or with offsetX and offsetY

canvas.addEventListener('mousemove', (e) => {
  const elementRelativeX = e.offsetX;
  const elementRelativeY = e.offsetY;
  const canvasRelativeX = elementRelativeX * canvas.width / canvas.clientWidth;
  const canvasRelativeY = elementRelativeY * canvas.height / canvas.clientHeight;
});

Note: In all cases do not add padding or borders to the canvas. Doing so will massively complicate the code. Instead of you want a border or padding surround the canvas in some other element and add the padding and or border to the outer element.

Working example using event.offsetX, event.offsetY

[...document.querySelectorAll('canvas')].forEach((canvas) => {
  const ctx = canvas.getContext('2d');
  ctx.canvas.width  = ctx.canvas.clientWidth;
  ctx.canvas.height = ctx.canvas.clientHeight;
  let count = 0;

  function draw(e, radius = 1) {
    const pos = {
      x: e.offsetX * canvas.width  / canvas.clientWidth,
      y: e.offsetY * canvas.height / canvas.clientHeight,
    };
    document.querySelector('#debug').textContent = count;
    ctx.beginPath();
    ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2);
    ctx.fillStyle = hsl((count++ % 100) / 100, 1, 0.5);
    ctx.fill();
  }

  function preventDefault(e) {
    e.preventDefault();
  }

  if (window.PointerEvent) {
    canvas.addEventListener('pointermove', (e) => {
      draw(e, Math.max(Math.max(e.width, e.height) / 2, 1));
    });
    canvas.addEventListener('touchstart', preventDefault, {passive: false});
    canvas.addEventListener('touchmove', preventDefault, {passive: false});
  } else {
    canvas.addEventListener('mousemove', draw);
    canvas.addEventListener('mousedown', preventDefault);
  }
});

function hsl(h, s, l) {
  return `hsl(${h * 360 | 0},${s * 100 | 0}%,${l * 100 | 0}%)`;
}
.scene {
  width: 200px;
  height: 200px;
  perspective: 600px;
}

.cube {
  width: 100%;
  height: 100%;
  position: relative;
  transform-style: preserve-3d;
  animation-duration: 16s;
  animation-name: rotate;
  animation-iteration-count: infinite;
  animation-timing-function: linear;
}

@keyframes rotate {
  from { transform: translateZ(-100px) rotateX(  0deg) rotateY(  0deg); }
  to   { transform: translateZ(-100px) rotateX(360deg) rotateY(720deg); }
}

.cube__face {
  position: absolute;
  width: 200px;
  height: 200px;
  display: block;
}

.cube__face--front  { background: rgba(255, 0, 0, 0.2); transform: rotateY(  0deg) translateZ(100px); }
.cube__face--right  { background: rgba(0, 255, 0, 0.2); transform: rotateY( 90deg) translateZ(100px); }
.cube__face--back   { background: rgba(0, 0, 255, 0.2); transform: rotateY(180deg) translateZ(100px); }
.cube__face--left   { background: rgba(255, 255, 0, 0.2); transform: rotateY(-90deg) translateZ(100px); }
.cube__face--top    { background: rgba(0, 255, 255, 0.2); transform: rotateX( 90deg) translateZ(100px); }
.cube__face--bottom { background: rgba(255, 0, 255, 0.2); transform: rotateX(-90deg) translateZ(100px); }
<div class="scene">
  <div class="cube">
    <canvas class="cube__face cube__face--front"></canvas>
    <canvas class="cube__face cube__face--back"></canvas>
    <canvas class="cube__face cube__face--right"></canvas>
    <canvas class="cube__face cube__face--left"></canvas>
    <canvas class="cube__face cube__face--top"></canvas>
    <canvas class="cube__face cube__face--bottom"></canvas>
  </div>
</div>
<pre id="debug"></pre>

Working example using canvas.getBoundingClientRect and event.clientX and event.clientY

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.canvas.width  = ctx.canvas.clientWidth;
ctx.canvas.height = ctx.canvas.clientHeight;
let count = 0;

function draw(e, radius = 1) {
  const rect = canvas.getBoundingClientRect();
  const pos = {
    x: (e.clientX - rect.left) * canvas.width  / canvas.clientWidth,
    y: (e.clientY - rect.top) * canvas.height / canvas.clientHeight,
  };
  ctx.beginPath();
  ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2);
  ctx.fillStyle = hsl((count++ % 100) / 100, 1, 0.5);
  ctx.fill();
}

function preventDefault(e) {
  e.preventDefault();
}

if (window.PointerEvent) {
  canvas.addEventListener('pointermove', (e) => {
    draw(e, Math.max(Math.max(e.width, e.height) / 2, 1));
  });
  canvas.addEventListener('touchstart', preventDefault, {passive: false});
  canvas.addEventListener('touchmove', preventDefault, {passive: false});
} else {
  canvas.addEventListener('mousemove', draw);
  canvas.addEventListener('mousedown', preventDefault);
}

function hsl(h, s, l) {
  return `hsl(${h * 360 | 0},${s * 100 | 0}%,${l * 100 | 0}%)`;
}
canvas { background: #FED; }
<canvas width="400" height="100" style="width: 300px; height: 200px"></canvas>
<div>canvas deliberately has differnt CSS size vs drawingbuffer size</div>
Salmonoid answered 19/5, 2019 at 7:0 Comment(5)
This worked well for me. One comment, the offsetX/Y version of the code has two typos when handling Y values. The fixed lines are: const elementRelativeY = e.offsetY; const canvasRelativeY = elementRelativeY * canvas.height / canvas.clientHeight;Afloat
thanks. fixed... note: you could have edited the answer. Your edit will be reviewedSalmonoid
also see Mozilla's mousedown event examplePatriapatriarch
the first one on the 2D canvas API worked for me nicely :)Hemichordate
I just wanted to add a comment saying out of all the answers - this is the only correct one. It accounts for a canvas being positioned anywhere at any zoom, and having the canvas internal scale of any values.Saavedra
M
18

According to fresh Quirksmode the clientX and clientY methods are supported in all major browsers. So, here it goes - the good, working code that works in a scrolling div on a page with scrollbars:

function getCursorPosition(canvas, event) {
var x, y;

canoffset = $(canvas).offset();
x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft - Math.floor(canoffset.left);
y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop - Math.floor(canoffset.top) + 1;

return [x,y];
}

This also requires jQuery for $(canvas).offset().

Micrometeorology answered 24/3, 2011 at 10:44 Comment(1)
Equivalent to @Salol answer.Timely
S
13

I made a full demostration that works in every browser with the full source code of the solution of this problem: Coordinates of a mouse click on Canvas in Javascript. To try the demo, copy the code and paste it into a text editor. Then save it as example.html and, finally, open the file with a browser.

Selene answered 8/5, 2011 at 15:57 Comment(0)
M
12

Here is a small modification to Ryan Artecona's answer for canvases with a variable (%) width:

 HTMLCanvasElement.prototype.relMouseCoords = function (event) {
    var totalOffsetX = 0;
    var totalOffsetY = 0;
    var canvasX = 0;
    var canvasY = 0;
    var currentElement = this;

    do {
        totalOffsetX += currentElement.offsetLeft;
        totalOffsetY += currentElement.offsetTop;
    }
    while (currentElement = currentElement.offsetParent)

    canvasX = event.pageX - totalOffsetX;
    canvasY = event.pageY - totalOffsetY;

    // Fix for variable canvas width
    canvasX = Math.round( canvasX * (this.width / this.offsetWidth) );
    canvasY = Math.round( canvasY * (this.height / this.offsetHeight) );

    return {x:canvasX, y:canvasY}
}
Manama answered 1/4, 2012 at 2:21 Comment(0)
D
7

Be wary while doing the coordinate conversion; there are multiple non-cross-browser values returned in a click event. Using clientX and clientY alone are not sufficient if the browser window is scrolled (verified in Firefox 3.5 and Chrome 3.0).

This quirks mode article provides a more correct function that can use either pageX or pageY or a combination of clientX with document.body.scrollLeft and clientY with document.body.scrollTop to calculate the click coordinate relative to the document origin.

UPDATE: Additionally, offsetLeft and offsetTop are relative to the padded size of the element, not the interior size. A canvas with the padding: style applied will not report the top-left of its content region as offsetLeft. There are various solutions to this problem; the simplest one may be to clear all border, padding, etc. styles on the canvas itself and instead apply them to a box containing the canvas.

Demibastion answered 4/10, 2009 at 14:37 Comment(0)
P
7

I'm not sure what's the point of all these answers that loop through parent elements and do all kinds of weird stuff.

The HTMLElement.getBoundingClientRect method is designed to to handle actual screen position of any element. This includes scrolling, so stuff like scrollTop is not needed:

(from MDN) The amount of scrolling that has been done of the viewport area (or any other scrollable element) is taken into account when computing the bounding rectangle

Normal image

The very simplest approach was already posted here. This is correct as long as no wild CSS rules are involved.

Handling stretched canvas/image

When image pixel width isn't matched by it's CSS width, you'll need to apply some ratio on pixel values:

/* Returns pixel coordinates according to the pixel that's under the mouse cursor**/
HTMLCanvasElement.prototype.relativeCoords = function(event) {
  var x,y;
  //This is the current screen rectangle of canvas
  var rect = this.getBoundingClientRect();
  var top = rect.top;
  var bottom = rect.bottom;
  var left = rect.left;
  var right = rect.right;
  //Recalculate mouse offsets to relative offsets
  x = event.clientX - left;
  y = event.clientY - top;
  //Also recalculate offsets of canvas is stretched
  var width = right - left;
  //I use this to reduce number of calculations for images that have normal size 
  if(this.width!=width) {
    var height = bottom - top;
    //changes coordinates by ratio
    x = x*(this.width/width);
    y = y*(this.height/height);
  } 
  //Return as an array
  return [x,y];
}

As long as the canvas has no border, it works for stretched images (jsFiddle).

Handling CSS borders

If the canvas has thick border, the things get little complicated. You'll literally need to subtract the border from the bounding rectangle. This can be done using .getComputedStyle. This answer describes the process.

The function then grows up a little:

/* Returns pixel coordinates according to the pixel that's under the mouse cursor**/
HTMLCanvasElement.prototype.relativeCoords = function(event) {
  var x,y;
  //This is the current screen rectangle of canvas
  var rect = this.getBoundingClientRect();
  var top = rect.top;
  var bottom = rect.bottom;
  var left = rect.left;
  var right = rect.right;
  //Subtract border size
  // Get computed style
  var styling=getComputedStyle(this,null);
  // Turn the border widths in integers
  var topBorder=parseInt(styling.getPropertyValue('border-top-width'),10);
  var rightBorder=parseInt(styling.getPropertyValue('border-right-width'),10);
  var bottomBorder=parseInt(styling.getPropertyValue('border-bottom-width'),10);
  var leftBorder=parseInt(styling.getPropertyValue('border-left-width'),10);
  //Subtract border from rectangle
  left+=leftBorder;
  right-=rightBorder;
  top+=topBorder;
  bottom-=bottomBorder;
  //Proceed as usual
  ...
}

I can't think of anything that would confuse this final function. See yourself at JsFiddle.

Notes

If you don't like modifying the native prototypes, just change the function and call it with (canvas, event) (and replace any this with canvas).

Petronius answered 29/11, 2014 at 17:27 Comment(0)
M
6

Here is a very nice tutorial-

http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/

 <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);

hope this helps!

Maclaine answered 1/7, 2013 at 10:10 Comment(2)
Ryan Artecona's solution didn't work for tablet browsers with zoom. However this one did.Dieball
Doesn't work with images that have CSS width/height overriden but still one of the best solutions.Preen
E
3

Using jQuery in 2016, to get click coordinates relative to the canvas, I do:

$(canvas).click(function(jqEvent) {
    var coords = {
        x: jqEvent.pageX - $(canvas).offset().left,
        y: jqEvent.pageY - $(canvas).offset().top
    };
});

This works since both canvas offset() and jqEvent.pageX/Y are relative to the document regardless of scroll position.

Note that if your canvas is scaled then these coordinates are not the same as canvas logical coordinates. To get those, you would also do:

var logicalCoords = {
    x: coords.x * (canvas.width / $(canvas).width()),
    y: coords.y * (canvas.height / $(canvas).height())
}
Effete answered 14/1, 2016 at 3:23 Comment(3)
Wow. How does 'jqEvent' become defined? Or 'canvas'? Or in the second example 'coords'? Do you need to run the first example before the second? In that case, why wouldn't you write "to get those, you would ALSO do"? Does this all go in an onclick function or what? Give a little context, mate. And considering the original question was asked in 2008, I think you need to answer in the context of the technology that was available in 2008. Refine your answer using valid jQuery from the version that was available at the time (v1.2). ;)Eichmann
OK, sorry for my presumptuousness. I will edit to remove the that. I did intend to provide the answer using the most recent frameworks. And I believe a programmer would not need to be explained what jqEvent, canvas, and coords are.Effete
Looks good. Thanks for your input! Sorry I gave you a hard time! ;)Eichmann
T
2

I recommend this link- http://miloq.blogspot.in/2011/05/coordinates-mouse-click-canvas.html

<style type="text/css">

  #canvas{background-color: #000;}

</style>

<script type="text/javascript">

  document.addEventListener("DOMContentLoaded", init, false);

  function init()
  {
    var canvas = document.getElementById("canvas");
    canvas.addEventListener("mousedown", getPosition, false);
  }

  function getPosition(event)
  {
    var x = new Number();
    var y = new Number();
    var canvas = document.getElementById("canvas");

    if (event.x != undefined && event.y != undefined)
    {
      x = event.x;
      y = event.y;
    }
    else // Firefox method to get the position
    {
      x = event.clientX + document.body.scrollLeft +
          document.documentElement.scrollLeft;
      y = event.clientY + document.body.scrollTop +
          document.documentElement.scrollTop;
    }

    x -= canvas.offsetLeft;
    y -= canvas.offsetTop;

    alert("x: " + x + "  y: " + y);
  }

</script>
Tavi answered 2/7, 2013 at 5:40 Comment(1)
what is the point of x = new Number()? The code below that reassigns x which means the Number allocated is just immediately discardedSalmonoid
C
1

In Prototype, use cumulativeOffset() to do the recursive summation as mentioned by Ryan Artecona above.

http://www.prototypejs.org/api/element/cumulativeoffset

Collincolline answered 26/7, 2011 at 23:10 Comment(0)
S
1

You could just do:

var canvas = yourCanvasElement;
var mouseX = (event.clientX - (canvas.offsetLeft - canvas.scrollLeft)) - 2;
var mouseY = (event.clientY - (canvas.offsetTop - canvas.scrollTop)) - 2;

This will give you the exact position of the mouse pointer.

Scummy answered 2/11, 2013 at 10:2 Comment(0)
R
1

See demo at http://jsbin.com/ApuJOSA/1/edit?html,output .

  function mousePositionOnCanvas(e) {
      var el=e.target, c=el;
      var scaleX = c.width/c.offsetWidth || 1;
      var scaleY = c.height/c.offsetHeight || 1;

      if (!isNaN(e.offsetX)) 
          return { x:e.offsetX*scaleX, y:e.offsetY*scaleY };

      var x=e.pageX, y=e.pageY;
      do {
        x -= el.offsetLeft;
        y -= el.offsetTop;
        el = el.offsetParent;
      } while (el);
      return { x: x*scaleX, y: y*scaleY };
  }
Ranchod answered 1/12, 2013 at 8:5 Comment(0)
C
1

I was creating an application having a canvas over a pdf, that involved a lot of resizes of canvas like Zooming the pdf-in and out, and in turn on every zoom-in/out of PDF I had to resize the canvas to adapt the size of the pdf, I went through lot of answers in stackOverflow, and didn't found a perfect solution that will eventually solve the problem.

I was using rxjs and angular 6, and didn't found any answer specific to the newest version.

Here is the entire code snippet that would be helpful, to anyone leveraging rxjs to draw on top of canvas.

  private captureEvents(canvasEl: HTMLCanvasElement) {

    this.drawingSubscription = fromEvent(canvasEl, 'mousedown')
      .pipe(
        switchMap((e: any) => {

          return fromEvent(canvasEl, 'mousemove')
            .pipe(
              takeUntil(fromEvent(canvasEl, 'mouseup').do((event: WheelEvent) => {
                const prevPos = {
                  x: null,
                  y: null
                };
              })),

              takeUntil(fromEvent(canvasEl, 'mouseleave')),
              pairwise()
            )
        })
      )
      .subscribe((res: [MouseEvent, MouseEvent]) => {
        const rect = this.cx.canvas.getBoundingClientRect();
        const prevPos = {
          x: Math.floor( ( res[0].clientX - rect.left ) / ( rect.right - rect.left ) * this.cx.canvas.width ),
          y:  Math.floor( ( res[0].clientY - rect.top ) / ( rect.bottom - rect.top ) * this.cx.canvas.height )
        };
        const currentPos = {
          x: Math.floor( ( res[1].clientX - rect.left ) / ( rect.right - rect.left ) * this.cx.canvas.width ),
          y: Math.floor( ( res[1].clientY - rect.top ) / ( rect.bottom - rect.top ) * this.cx.canvas.height )
        };

        this.coordinatesArray[this.file.current_slide - 1].push(prevPos);
        this.drawOnCanvas(prevPos, currentPos);
      });
  }

And here is the snippet that fixes, mouse coordinates relative to size of the canvas, irrespective of how you zoom-in/out the canvas.

const prevPos = {
  x: Math.floor( ( res[0].clientX - rect.left ) / ( rect.right - rect.left ) * this.cx.canvas.width ),
  y:  Math.floor( ( res[0].clientY - rect.top ) / ( rect.bottom - rect.top ) * this.cx.canvas.height )
};
const currentPos = {
  x: Math.floor( ( res[1].clientX - rect.left ) / ( rect.right - rect.left ) * this.cx.canvas.width ),
  y: Math.floor( ( res[1].clientY - rect.top ) / ( rect.bottom - rect.top ) * this.cx.canvas.height )
};
Cushion answered 29/5, 2019 at 14:57 Comment(0)
A
0

Here is some modifications of the above Ryan Artecona's solution.

function myGetPxStyle(e,p)
{
    var r=window.getComputedStyle?window.getComputedStyle(e,null)[p]:"";
    return parseFloat(r);
}

function myGetClick=function(ev)
{
    // {x:ev.layerX,y:ev.layerY} doesn't work when zooming with mac chrome 27
    // {x:ev.clientX,y:ev.clientY} not supported by mac firefox 21
    // document.body.scrollLeft and document.body.scrollTop seem required when scrolling on iPad
    // html is not an offsetParent of body but can have non null offsetX or offsetY (case of wordpress 3.5.1 admin pages for instance)
    // html.offsetX and html.offsetY don't work with mac firefox 21

    var offsetX=0,offsetY=0,e=this,x,y;
    var htmls=document.getElementsByTagName("html"),html=(htmls?htmls[0]:0);

    do
    {
        offsetX+=e.offsetLeft-e.scrollLeft;
        offsetY+=e.offsetTop-e.scrollTop;
    } while (e=e.offsetParent);

    if (html)
    {
        offsetX+=myGetPxStyle(html,"marginLeft");
        offsetY+=myGetPxStyle(html,"marginTop");
    }

    x=ev.pageX-offsetX-document.body.scrollLeft;
    y=ev.pageY-offsetY-document.body.scrollTop;
    return {x:x,y:y};
}
Airglow answered 18/6, 2013 at 20:24 Comment(0)
P
0

First, as others have said, you need a function to get the position of the canvas element. Here's a method that's a little more elegant than some of the others on this page (IMHO). You can pass it any element and get its position in the document:

function findPos(obj) {
    var curleft = 0, curtop = 0;
    if (obj.offsetParent) {
        do {
            curleft += obj.offsetLeft;
            curtop += obj.offsetTop;
        } while (obj = obj.offsetParent);
        return { x: curleft, y: curtop };
    }
    return undefined;
}

Now calculate the current position of the cursor relative to that:

$('#canvas').mousemove(function(e) {
    var pos = findPos(this);
    var x = e.pageX - pos.x;
    var y = e.pageY - pos.y;
    var coordinateDisplay = "x=" + x + ", y=" + y;
    writeCoordinateDisplay(coordinateDisplay);
});

Notice that I've separated the generic findPos function from the event handling code. (As it should be. We should try to keep our functions to one task each.)

The values of offsetLeft and offsetTop are relative to offsetParent, which could be some wrapper div node (or anything else, for that matter). When there is no element wrapping the canvas they're relative to the body, so there is no offset to subtract. This is why we need to determine the position of the canvas before we can do anything else.

Similary, e.pageX and e.pageY give the position of the cursor relative to the document. That's why we subtract the canvas's offset from those values to arrive at the true position.

An alternative for positioned elements is to directly use the values of e.layerX and e.layerY. This is less reliable than the method above for two reasons:

  1. These values are also relative to the entire document when the event does not take place inside a positioned element
  2. They are not part of any standard
Prankster answered 5/1, 2014 at 6:26 Comment(0)
I
0

ThreeJS r77

var x = event.offsetX == undefined ? event.layerX : event.offsetX;
var y = event.offsetY == undefined ? event.layerY : event.offsetY;

mouse2D.x = ( x / renderer.domElement.width ) * 2 - 1;
mouse2D.y = - ( y / renderer.domElement.height ) * 2 + 1;

After trying many solutions. This worked for me. Might help someone else hence posting. Got it from here

Insphere answered 13/6, 2016 at 12:49 Comment(0)
F
0

Here is a simplified solution (this doesn't work with borders/scrolling):

function click(event) {
    const bound = event.target.getBoundingClientRect();

    const xMult = bound.width / can.width;
    const yMult = bound.height / can.height;

    return {
        x: Math.floor(event.offsetX / xMult),
        y: Math.floor(event.offsetY / yMult),
    };
}
Freeze answered 24/1, 2019 at 23:15 Comment(0)
B
-2

Hey, this is in dojo, just cause it's what I had the code in already for a project.

It should be fairly Obvious how to convert it back to non dojo vanilla JavaScript.

  function onMouseClick(e) {
      var x = e.clientX;
      var y = e.clientY;
  }
  var canvas = dojo.byId(canvasId);
  dojo.connect(canvas,"click",onMouseClick);

Hope that helps.

Boutique answered 11/9, 2008 at 2:49 Comment(1)
clientX/clientY do not behave similarly across browsers.Montero

© 2022 - 2024 — McMap. All rights reserved.