JavaScript Cursor Position January 23, 2006

Update: I submitted this to MochiKit while building Snipshot, a web tool to edit pictures online.

I discovered that IE’s clientX and clientY measurements were sometimes a couple pixels out. It turns out this is because IE’s clientX and clientY measurements start from (2,2) in standards mode, and (0,0) in quirks mode.

IE stores this offset in its document.documentElement.clientLeft and document.documentElement.clientTop properties. This code should calculate the correct cursor position in all current browsers:

function getPosition(e) {
    e = e || window.event;
    var cursor = {x:0, y:0};
    if (e.pageX || e.pageY) {
        cursor.x = e.pageX;
        cursor.y = e.pageY;
    } 
    else {
        var de = document.documentElement;
        var b = document.body;
        cursor.x = e.clientX + 
            (de.scrollLeft || b.scrollLeft) - (de.clientLeft || 0);
        cursor.y = e.clientY + 
            (de.scrollTop || b.scrollTop) - (de.clientTop || 0);
    }
    return cursor;
}
 
JavaScript Positioning January 20, 2006

To find out exactly how browsers were interpreting offsetLeft and offsetTop, I built a test page that would tell me I added a 1px margin, 2px border, 4px padding, 8px position, 16px margin, and so on and then used bitwise AND to figure out which parts were being included in the total calculation. what parts of the style calculation were included.

It’s a mess. Firefox and Opera do the calculation in the same way, while Safari and IE each have unique interpretations. IE has the only obvious bug, where the top padding of an element is not included in one calculation, while the left padding is.

The only thing that appears to work consistently is finding an element’s absolute position on a page—as long as no elements have any borders, margins, or padding.

It is possible to get the browsers a little closer to the correct answer if you include the IE-specific clientTop and clientLeft properties. Here’s a test page to demonstrates this. In this case, Safari, IE, and Opera agree on the absolute x coordinate, IE still won’t include y padding, and Firefox is on its own.

I think this comment sums up the problem nicely:

“The problem to begin with, the origin of the problem is that MSDN does not define strictly, explicitly how offsetLeft/offsetTop should be computed in normal situations and in various other situations. Even offsetParent is not defined clearly: there is no precise spec. - formal rigorous spec - identifying how the offsetParent can be, should be determined. offsetLeft, offsetTop definitions are circularly dependent, self-inter-dependent to offsetParent definition.

Add to this quirks mode vs standards rendering mode, and other properties (padding, border, margin, box-sizing) which interfere or affect too calculations of offsetLeft, offsetTop or which may influence too the way offsetParent is to be defined and then you have a real mess, headache, impossible task.”

 
JavaScript Events January 18, 2006

I’m trying to help out the MochiKit and Dojo projects by writing an event normalizer. I’m tired of writing or finding one-off functions to solve this problem, and want something that will return accurate cursor and element coordinates.

I built a test page to collect seven browsers’ interpretations of a set of event properties. The only event property I normalized was event.target, I left everything else alone. The proxy.* values are calculated based on some assumptions I had about how the browsers interpreted the target’s position (proxy.pos), the cursor position relative to the document (proxy.page), the cursor position relative to the target (proxy.targetOffset), and the cursor position relative to the target’s positioning context (proxy.contextOffset). I used Pixie (Mac) and ZoomPlusU (PC) to position the cursor on every test.

My code still needs some work.

Note that Firefox 1.5 (Gecko 2005-11-11) introduced an off-by-one bug to the event.layerX and event.layerY properties. It’s been fixed in the latest nightly builds.

Also note that IE’s clientX and clientY properties are off by two pixels. From MSDN: “In Microsoft Internet Explorer 5, the window's upper-left is at 2,2 (pixels) with respect to the true client.” The proxy.page.x/y and .targetOffset.x/y variables seem to be relatively consistent up until test point five, when I measure point (1,1) in the document's first absolutely positioned element.

A lot of libraries do something like:

event.layerX = event.layerX || event.offsetX

This is incorrect, layerX and offsetX are not equivalent.

Here is the test page. (Hold down the alt key and click on any part of the page to get a variable dump.) I did nine manual tests at different points in the document, starting from the top left corner of the document. I’ve included Pixie screenshots to show where I’m taking my sample.

There is still a lot of work to do, and I would really appreciate your feedback on what I’ve got so far. You can reach me at beau@hartshorne.ca.

One

one.png
Apple WebKit / 417.9 Safari / 417.8Apple WebKit / 420+ Safari / 417.8Gecko / 2005-09-15 Firefox / 1.0.7Gecko / 2005-11-11 Firefox / 1.5Gecko / 2006-01-17 Firefox / 1.6a1MSIE / 6.0. 2900. 2180Opera 8.5 / Build 2173
event.target.idhtmlhtmlhtmlhtmlhtmlhtmlhtml
event.pageX11111-1
event.pageY11111-1
event.offsetX11---11
event.offsetY11---11
event.layerX11121--
event.layerY11121--
event.clientX1111131
event.clientY1111131
event.x11---31
event.y11---31
target.offsetLeft0000000
target.offsetTop0000000
window.innerHeight527417620617617-617
window.innerWidth802713860860860-1085
window.pageXOffset00000-0
window.pageYOffset00000-0
document. documentElement. scrollLeft0000000
document. body.scrollLeft0000000
document. documentElement. scrollTop0000000
document. body.scrollTop0000000
proxy.pos.x0000000
proxy.pos.y0000000
proxy.page.x1111131
proxy.page.y1111131
proxy.targetOffset.x1111131
proxy.targetOffset.y1111131
proxy.contextOffset.x1111131
proxy.contextOffset.y1111131

Two

two.png
  Apple WebKit / 417.9 Safari / 417.8Apple WebKit / 420+ Safari / 417.8Gecko / 2005-09-15 Firefox / 1.0.7Gecko / 2005-11-11 Firefox / 1.5Gecko / 2006-01-17 Firefox / 1.6a1MSIE / 6.0. 2900. 2180Opera 8.5 / Build 2173
event.target.idbodybodybodybodybodybodybody
event.pageX1111111111-11
event.pageY1111111111-11
event.offsetX111----9-19
event.offsetY111----9-19
event.layerX1111111211--
event.layerY1111111211--
event.clientX11111111111311
event.clientY11111111111311
event.x1111---1311
event.y1111---1311
target.offsetLeft1010-10-10-101010
target.offsetTop1010-10-10-101010
window.innerHeight527417620617617-617
window.innerWidth802713860860860-1085
window.pageXOffset00000-0
window.pageYOffset00000-0
document. documentElement. scrollLeft0000000
document. body.scrollLeft0000000
document. documentElement. scrollTop0000000
document. body.scrollTop0000000
proxy.pos.x0000000
proxy.pos.y0000000
proxy.page.x11111111111311
proxy.page.y11111111111311
proxy.targetOffset.x11111111111311
proxy.targetOffset.y11111111111311
proxy.contextOffset.x21211112321
proxy.contextOffset.y21211112321

Three

three.png
  Apple WebKit / 417.9 Safari / 417.8Apple WebKit / 420+ Safari / 417.8Gecko / 2005-09-15 Firefox / 1.0.7Gecko / 2005-11-11 Firefox / 1.5Gecko / 2006-01-17 Firefox / 1.6a1MSIE / 6.0. 2900. 2180Opera 8.5 / Build 2173
event.target.idbodybodybodybodybodybodybody
event.pageX2121212121-21
event.pageY2121212121-21
event.offsetX1121---1-9
event.offsetY1121---1-9
event.layerX2121212221--
event.layerY2121212221--
event.clientX21212121212321
event.clientY21212121212321
event.x2121---2321
event.y2121---2321
target.offsetLeft1010-10-10-101010
target.offsetTop1010-10-10-101010
window.innerHeight527417620617617-617
window.innerWidth802713860860860-1085
window.pageXOffset00000-0
window.pageYOffset00000-0
document. documentElement. scrollLeft0000000
document. body.scrollLeft0000000
document. documentElement. scrollTop0000000
document. body.scrollTop0000000
proxy.pos.x0000000
proxy.pos.y0000000
proxy.page.x21212121212321
proxy.page.y21212121212321
proxy.targetOffset.x21212121212321
proxy.targetOffset.y21212121212321
proxy.contextOffset.x31311111113331
proxy.contextOffset.y31311111113331

Four

four.png
  Apple WebKit / 417.9 Safari / 417.8Apple WebKit / 420+ Safari / 417.8Gecko / 2005-09-15 Firefox / 1.0.7Gecko / 2005-11-11 Firefox / 1.5Gecko / 2006-01-17 Firefox / 1.6a1MSIE / 6.0. 2900. 2180Opera 8.5 / Build 2173
event.target.idoneoneoneoneoneoneone
event.pageX10011001100110011001-1001
event.pageY10011001100110011001-1001
event.offsetX11001----9-19
event.offsetY11001----9-19
event.layerX11121--
event.layerY11121--
event.clientX10011001576576576551801
event.clientY10011001336333333256361
event.x10011001---551801
event.y10011001---256361
target.offsetLeft1000100010001000100010001000
target.offsetTop1000100010001000100010001000
window.innerHeight527417620617617-617
window.innerWidth802713860860860-1085
window.pageXOffset468557425425425-200
window.pageYOffset736863665668668-640
document. documentElement. scrollLeft00425425425452200
document. body.scrollLeft4685570000200
document. documentElement. scrollTop00665668668747640
document. body.scrollTop7368630000640
proxy.pos.x1000100010001000100010001000
proxy.pos.y1000100010001000100010001000
proxy.page.x1001100110011001100110031001
proxy.page.y1001100110011001100110031001
proxy.targetOffset.x1111131
proxy.targetOffset.y1111131
proxy.contextOffset.x1001100110011001100110031001
proxy.contextOffset.y1001100110011001100110031001

Five

five.png
  Apple WebKit / 417.9 Safari / 417.8Apple WebKit / 420+ Safari / 417.8Gecko / 2005-09-15 Firefox / 1.0.7Gecko / 2005-11-11 Firefox / 1.5Gecko / 2006-01-17 Firefox / 1.6a1MSIE / 6.0. 2900. 2180Opera 8.5 / Build 2173
event.target.idoneoneoneoneoneoneone
event.pageX10111011101110111011-1011
event.pageY10111011101110111011-1011
event.offsetX111011---1-9
event.offsetY111011---1-9
event.layerX1111111211--
event.layerY1111111211--
event.clientX10111011586586586561811
event.clientY10111011346343343266371
event.x10111011---561811
event.y10111011---266371
target.offsetLeft1000100010001000100010001000
target.offsetTop1000100010001000100010001000
window.innerHeight527417620617617-617
window.innerWidth802713860860860-1085
window.pageXOffset468557425425425-200
window.pageYOffset736863665668668-640
document. documentElement. scrollLeft00425425425452200
document. body.scrollLeft4685570000200
document. documentElement. scrollTop00665668668747640
document. body.scrollTop7368630000640
proxy.pos.x1000100010001000100010001000
proxy.pos.y1000100010001000100010001000
proxy.page.x1011101110111011101110131011
proxy.page.y1011101110111011101110131011
proxy.targetOffset.x11111111111311
proxy.targetOffset.y11111111111311
proxy.contextOffset.x1011101110111011101110131011
proxy.contextOffset.y1011101110111011101110131011

Six

six.png
  Apple WebKit / 417.9 Safari / 417.8Apple WebKit / 420+ Safari / 417.8Gecko / 2005-09-15 Firefox / 1.0.7Gecko / 2005-11-11 Firefox / 1.5Gecko / 2006-01-17 Firefox / 1.6a1MSIE / 6.0. 2900. 2180Opera 8.5 / Build 2173
event.target.idtwotwotwotwotwotwotwo
event.pageX10311031103110311031-1031
event.pageY10311031103110311031-1031
event.offsetX11031----9-19
event.offsetY11031----9-19
event.layerX3131313231--
event.layerY3131313231--
event.clientX10311031606606606581831
event.clientY10311031366363363276391
event.x10311031---581831
event.y10311031---276391
target.offsetLeft30302020202020
target.offsetTop30302020201020
window.innerHeight527417620617617-617
window.innerWidth802713860860860-1085
window.pageXOffset468557425425425-200
window.pageYOffset736863665668668-640
document. documentElement. scrollLeft00425425425452200
document. body.scrollLeft4685570000200
document. documentElement. scrollTop00665668668747640
document. body.scrollTop7368630000640
proxy.pos.x1030103010201020102010201020
proxy.pos.y1030103010201020102010101020
proxy.page.x1031103110311031103110331031
proxy.page.y1031103110311031103110231031
proxy.targetOffset.x111111111311
proxy.targetOffset.y111111111311
proxy.contextOffset.x31313131313331
proxy.contextOffset.y31313131312331

Seven

seven.png
  Apple WebKit / 417.9 Safari / 417.8Apple WebKit / 420+ Safari / 417.8Gecko / 2005-09-15 Firefox / 1.0.7Gecko / 2005-11-11 Firefox / 1.5Gecko / 2006-01-17 Firefox / 1.6a1MSIE / 6.0. 2900. 2180Opera 8.5 / Build 2173
event.target.idtwotwotwotwotwotwotwo
event.pageX10411041104110411041-1041
event.pageY10411041104110411041-1041
event.offsetX111041---1-9
event.offsetY111041---1-9
event.layerX4141414241--
event.layerY4141414241--
event.clientX10411041616616616591841
event.clientY10411041376373373286401
event.x10411041---591841
event.y10411041---286401
target.offsetLeft30302020202020
target.offsetTop30302020201020
window.innerHeight527417620617617-617
window.innerWidth802713860860860-1085
window.pageXOffset468557425425425-200
window.pageYOffset736863665668668-640
document. documentElement. scrollLeft00425425425452200
document. body.scrollLeft4685570000200
document. documentElement. scrollTop00665668668747640
document. body.scrollTop7368630000640
proxy.pos.x1030103010201020102010201020
proxy.pos.y1030103010201020102010101020
proxy.page.x1041104110411041104110431041
proxy.page.y1041104110411041104110331041
proxy.targetOffset.x11112121212321
proxy.targetOffset.y11112121212321
proxy.contextOffset.x41414141414341
proxy.contextOffset.y41414141413341

Eight

eight.png
  Apple WebKit / 417.9 Safari / 417.8Apple WebKit / 420+ Safari / 417.8Gecko / 2005-09-15 Firefox / 1.0.7Gecko / 2005-11-11 Firefox / 1.5Gecko / 2006-01-17 Firefox / 1.6a1MSIE / 6.0. 2900. 2180Opera 8.5 / Build 2173
event.target.idthreethreethreethreethreethreethree
event.pageX11911191119111911191-1191
event.pageY10611061106110611061-1061
event.offsetX11191----9-19
event.offsetY11061----9-19
event.layerX191191191192191--
event.layerY6161616261--
event.clientX11911191766766766731991
event.clientY10611061396393393306421
event.x11911191---731991
event.y10611061---306421
target.offsetLeft190190180180180140180
target.offsetTop60605050502050
window.innerHeight527417620617617-617
window.innerWidth802713860860860-1085
window.pageXOffset468557425425425-200
window.pageYOffset736863665668668-640
document. documentElement. scrollLeft00425425425452200
document. body.scrollLeft4685570000200
document. documentElement. scrollTop00665668668747640
document. body.scrollTop7368630000640
proxy.pos.x1190119011801180118011601180
proxy.pos.y1060106010501050105010301050
proxy.page.x1191119111911191119111831191
proxy.page.y1061106110611061106110531061
proxy.targetOffset.x111111112311
proxy.targetOffset.y111111112311
proxy.contextOffset.x191191191191191163191
proxy.contextOffset.y61616161614361

Nine

nine.png
  Apple WebKit / 417.9 Safari / 417.8Apple WebKit / 420+ Safari / 417.8Gecko / 2005-09-15 Firefox / 1.0.7Gecko / 2005-11-11 Firefox / 1.5Gecko / 2006-01-17 Firefox / 1.6a1MSIE / 6.0. 2900. 2180Opera 8.5 / Build 2173
event.target.idthreethreethreethreethreethreethree
event.pageX12011201120112011201-1201
event.pageY10711071107110711071-1071
event.offsetX111201---1-9
event.offsetY111071---1-9
event.layerX201201201202201--
event.layerY7171717271--
event.clientX120112017767767767411001
event.clientY10711071406403403316431
event.x12011201---7411001
event.y10711071---316431
target.offsetLeft190190180180180140180
target.offsetTop60605050502050
window.innerHeight527417620617617-617
window.innerWidth802713860860860-1085
window.pageXOffset468557425425425-200
window.pageYOffset736863665668668-640
document. documentElement. scrollLeft00425425425452200
document. body.scrollLeft4685570000200
document. documentElement. scrollTop00665668668747640
document. body.scrollTop7368630000640
proxy.pos.x1190119011801180118011601180
proxy.pos.y1060106010501050105010301050
proxy.page.x1201120112011201120111931201
proxy.page.y1071107110711071107110631071
proxy.targetOffset.x11112121213321
proxy.targetOffset.y11112121213321
proxy.contextOffset.x201201201201201173201
proxy.contextOffset.y71717171715371