48
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script language="javascript">
function main(){
    var canvas = document.getElementById("canvas");
    canvas.addEventListener("mousemove", function(e){
        if (!e) e = window.event;
        var ctx = canvas.getContext("2d");

        var x = e.offsetX;
        var y = e.offsetY;

        ctx.fillRect(x, y, 1, 1);
    });
}
</script>
</head>
<body onload="main();">
<div style="width: 800px; height: 600px; -webkit-transform: scale(0.75,0.75); -moz-transform: scale(0.75,0.75)">
    <canvas id="canvas" width="400px" height="400px" style="background-color: #cccccc;"></canvas>
</div>
</body>
</html>

Please consider the above quick and dirty example. Please notice that my canvas is contained by a div having a scale transform applied. The above code works perfectly on any webkit based browser. While moving the mouse it draws points on the canvas. Unfortunately it doesn't in Firefox as its event model does not support the offsetX / Y properties. How can I transform mouse coordinates from (perhaps) event.clientX (which is supported in firefox too) into canvas relative coordinates taking into account canvas position, transform etc? Thanks, Luca.

lviggiani
  • 5,824
  • 12
  • 56
  • 89

12 Answers12

78

From a JQuery bug tracker page - a nice polyfill is this:

var offX  = (e.offsetX || e.pageX - $(e.target).offset().left);

.. where e is the event returned from a jquery event. Obviously, only if you've got Jquery already on your project, otherwise will have to do the offset() stuff manually.

Griffork
  • 683
  • 1
  • 5
  • 21
danp
  • 14,876
  • 6
  • 42
  • 48
  • 2
    wow. from all answers, only this one working for me. i've tried this with absolutely positioned element, thank you man so much! best answer in this topic – Denis Feb 22 '13 at 21:11
  • 15
    var eoffsetX = (e.offsetX || e.clientX - $(e.target).offset().left + window.pageXOffset ), eoffsetY = (e.offsetY || e.clientY - $(e.target).offset().top + window.pageYOffset ); this fix will help if you have scrolled the page – Denis Mar 21 '13 at 22:22
  • Thanks @Denis, your answer of adding `y position` really helped me in fixing my issue – Anil kumar May 10 '14 at 11:55
  • 9
    Bug tracker page has changed since the answer. It says you should use `e.pageX` instead of `e.clientX`. It fixes the problem with scrolled page, too. Works in latest Chrome, FF, and IE. – Milanka May 20 '14 at 13:55
  • Seems that FF, since version 39, now supports this property. Good news! – danp Oct 29 '15 at 12:44
36

Try layerX, layerY

var x = (e.offsetX === undefined) ? e.layerX : e.offsetX;
var y = (e.offsetY === undefined) ? e.layerY : e.offsetY;

FIDDLE

mix3d
  • 4,122
  • 2
  • 25
  • 47
Musa
  • 96,336
  • 17
  • 118
  • 137
  • 2
    Unfortunately, Firefox layerX and layerY properties are differs from ones in other browsers. There are relative top-left corner of page, but not elemenet. – Ivan Kochurkin Dec 03 '12 at 14:35
  • 5
    false, they are relative to the closest offsetParent including the element itself, just add "position:relative" to the element you want to use for coordenates. – Ivan Castellanos May 23 '13 at 09:40
  • Didn't work for me, but this worked http://stackoverflow.com/questions/12704686/html5-with-jquery-e-offsetx-is-undefined-in-firefox – Palani Feb 07 '14 at 10:58
  • Didn't work on FF 32.0.3 . layerX and layerY are absent from event. – cristiano2lopes Oct 09 '14 at 15:43
  • @Musa can you ammend this answer to note that `position:relative` is necessary to force these properties to be relative to the element, rather than an ancestor? – Mike 'Pomax' Kamermans Oct 14 '14 at 16:42
  • 1
    layerX and offsetX are not the same, if the target is an inline element, the offset give the position of the cursor in the element, but return a layerX relative to his non-inline parent container. – Adrian Maire Oct 27 '14 at 13:42
  • @cristiano2lopes if you are using jQuery, you may you have to use ```event.originalEvent.layerX```. – snapfractalpop Nov 26 '14 at 22:29
  • https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/layerX "This feature is non-standard and is not on a standards track. Do not use it on production sites facing the Web: it will not work for every user. There may also be large incompatibilities between implementations and the behavior may change in the future." – Andrew Aug 07 '20 at 19:27
21

Unfortunately no solution worked for me.

I found a good implementation here:

var target  = e.target || e.srcElement,
              rect    = target.getBoundingClientRect(),
              offsetX = e.clientX - rect.left,
              offsetY  = e.clientY - rect.top;

e.offsetX   = offsetX;
e.offsetY   = offsetY;
undefined
  • 6,208
  • 3
  • 49
  • 59
15

Unfortunately offsetX and layerX are not exactly the same as offsetX is the offset within the current element but layerX is the offset from the page. Below is a fix I am currently using for this:

function fixEvent(e) {
    if (! e.hasOwnProperty('offsetX')) {
        var curleft = curtop = 0;
        if (e.offsetParent) {
           var obj=e;
           do {
              curleft += obj.offsetLeft;
              curtop += obj.offsetTop;
           } while (obj = obj.offsetParent);
        }
        e.offsetX=e.layerX-curleft;
        e.offsetY=e.layerY-curtop;
    }
    return e;
}
Tom Fredian
  • 159
  • 1
  • 3
  • 3
    +1 for a non-jQuery solution that maintains the same semantics as `offsetX` and `offsetY`. – Drew Noakes Sep 29 '13 at 22:20
  • 2
    Under Firefox 26.0 there is no field named 'offsetParent' in the event (e) thus initial e.offsetParent does not work ('if' is simply ignored). A solution might be to have e.target.offsetParent – Alex Jan 24 '14 at 08:28
  • @Alex `e.target.offsetParent` doesn't work either. Did you come up with a fix for this? – Langdon Feb 05 '14 at 22:39
  • + 1 - this is the only solution that worked for me, with the only change being using e.target.offsetParent instead of e.offsetParent. I'm using RaphaelJS to draw objects in a map. With this code, I always get the right mouse coordinates even if e.target is sometimes the circle being drawn and sometimes the map canvas. – igelineau May 25 '15 at 20:46
11

There's a bug in Musa's solution: think what happens if e.offsetX === 0 and e.layerX === undefined...

var x = e.offsetX || e.layerX; // x is now undefined!

A more robust version is as follows:

var x = e.hasOwnProperty('offsetX') ? e.offsetX : e.layerX;
var y = e.hasOwnProperty('offsetY') ? e.offsetY : e.layerY;

Or, because we can assume that if offsetX is defined, offsetY will be too:

var hasOffset = e.hasOwnProperty('offsetX'),
    x         = hasOffset ? e.offsetX : e.layerX,
    y         = hasOffset ? e.offsetY : e.layerY;
Mark Whitaker
  • 8,465
  • 8
  • 44
  • 68
  • 1
    This doesn't work for me, because the firefox event has 'offsetX/Y' but both are undefined. For what it's worth. – jhoff Jul 20 '12 at 13:07
  • 1
    Bad Mozilla! In that case adjust appropriately - thanks for flagging it up. – Mark Whitaker Jul 21 '12 at 17:18
  • 9
    var x = e.offsetX || e.layerX || 0 ; is a simpler solution. – GameAlchemist Sep 21 '13 at 14:15
  • 2
    layerX and offsetX are not the same, if the target is an inline element, the offset give the position of the cursor in the element, but return a layerX relative to his non-inline parent container. – Adrian Maire Oct 27 '14 at 13:59
4

offset actually doesn't translate directly into layer; the offset property doesn't account for the element's margin. The code below should account for this.

function(e) {
    var x = e.offsetX, y = e.offsetY;
    if(e.hasOwnProperty('layerX')) {
      x = e.layerX - e.currentTarget.offsetLeft;
      y = e.layerY - e.currentTarget.offsetTop;
    }
}
EnotionZ
  • 96
  • 2
3

None of the non-jquery versions work completely for various reasons. With your help however i got this to work:

if(!event.hasOwnProperty('offsetX')) {
    event.offsetX = event.layerX - event.currentTarget.offsetLeft;
    event.offsetY = event.layerY - event.currentTarget.offsetTop;
}
br4nnigan
  • 646
  • 6
  • 13
3

I found that all answers posted here except the last two answers by EnotionZ and laz brannigan (previously with zero votes each) where wrong in cases where several elements were contained within a div. In my case I have several canvas elements inside of a single div and I am listening to each canvas separately.

After considerable trial and error the final correct answer I came to, which works perfectly and identically for me in FireFox and Chrome is as follows:

//inside my mouse events handler:
var anOffsetX = (inEvent.offsetX !== undefined) ? inEvent.offsetX : (inEvent.layerX - inEvent.target.offsetLeft);
var anOffsetY = (inEvent.offsetY !== undefined) ? inEvent.offsetY : (inEvent.layerY - inEvent.target.offsetTop);

Presumably this will also work for margins and such as EnotionZ indicated in his post, but I have not tried that.

2

But , what you will do if there is not layerX,layerY fields ?

 var xe=e.offsetX,ye=e.offsetY;
  if(!xe){
     xe=e.clientX - $(e.target).offset().left;
  }
  if(!ye){
    ye= e.clientY - $(e.target).offset().top;
  }
Abdennour TOUMI
  • 87,526
  • 38
  • 249
  • 254
2

OffsetX/Y behavior is different in FireFox, Chrome/Edge. You may need calculate the value by adding the code snippet:

const offsetX = evt.clientX - this.myRef.current.getBoundingClientRect().left;
const offsetY = evt.clientY - this.myRef.current.getBoundingClientRect().top;

The myRef is ref of its nearest parent element. So the offset will be the distance between this element (clientX) minus the one of its parent.

August
  • 116
  • 1
  • 3
1

This question needs an updated answer.


First, as I mentioned in a comment, regarding all of the older answers using layerX and layerY:

"This feature is non-standard and is not on a standards track. Do not use it on production sites facing the Web: it will not work for every user. There may also be large incompatibilities between implementations and the behavior may change in the future."

(Source: developer.mozilla.org/en-US/docs/Web/API/UIEvent/layerX)


Second, I've discovered that when you console.log(event) in Firefox, offsetX and offsetY show 0, but when you console.log(event.offsetX), it is not 0. So be careful because you may be being lied to.

This behavior is explained here:

Logging objects

Don't use console.log(obj), use console.log(JSON.parse(JSON.stringify(obj))).

This way you are sure you are seeing the value of obj at the moment you log it. Otherwise, many browsers provide a live view that constantly updates as values change. This may not be what you want.

(Also note that JSON.stringify() will not do what you want here... It doesn't stringify the entire event object.)

Andrew
  • 5,839
  • 1
  • 51
  • 72
  • For those who are still having problems with `offsetX` and `offsetY`, you may want to look into this: https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect – Andrew Aug 07 '20 at 19:49
0

If some still needs a solutions this one works perfect in Firefox:

var x = e.offsetX || e.originalEvent.layerX;
var y = e.offsetY || e.originalEvent.layerY;
Crisoforo Gaspar
  • 3,740
  • 2
  • 21
  • 27
  • 1
    should be noted that this is meant to be used inside a jquery event handler, as jquery wraps the original event in a custom object, with access to the former via ´originalEvent´ – schellmax Feb 12 '19 at 18:30