As I was working on the new recolorable Garland Drupal theme, I noticed that suddenly my Farbtastic color picker wasn't working right anymore in IE. A lot of headscratching later, I found the cause and discovered a useful trick for dealing with mouse coordinates in JavaScript.
Essentially, when you click on Farbtastic, the mouse position is compared to the position of the color picker, so we can determine which color you clicked. Sounds simple? Well, no. There is no direct DOM API to get an elements's absolute position on the page. The most common technique to find it is to iterate through an element's offsetParents until you reach the root, and add together all the offsets:
function getAbsolutePosition(element) {
var r = { x: element.offsetLeft, y: element.offsetTop };
if (element.offsetParent) {
var tmp = getAbsolutePosition(element.offsetParent);
r.x += tmp.x;
r.y += tmp.y;
}
return r;
};Unfortunately, even this does not work well. Various browsers have various quirks, but (no surprise) IE wins the contest hands down. When you try to resolve absolute positions in any sort of advanced CSS-based layout, the return coordinates are often completely wrong. This is exactly what was happening in the new (completely tableless) Garland theme.
After trying various ways to correct the absolute values, I decided I didn't want to waste hours of my life cleaning up after somebody elses mess. And of course, hardcoding in the correction is mostly useless in a dynamic CMS like Drupal.
I did come up with an alternative which works well enough, and is perfectly suited for making self-contained HTML widgets: that's the most common use case after all.
You see, aside from the absolute mouse position (event.pageX/Y) we often also get the mouse position relative to the clicked element (event.offsetX/Y). Now, if we try to resolve these coordinates back to the root of the page, we end up with the same problem. The trick is to realize that we often don't need completely absolute coordinates: all we need is coordinates relative to a common reference frame. So, we need to find the closest, common offsetParent for the clicked element and the reference element, and then compare the coordinates in that frame.
The snippet below achieves this. As most of the bad offsetParent numbers are located very high up in the page hierarchy, they are practically never used with this approach. Typically you only go up one or two offsetParents and there is no error.
Some browsers don't provide the offsetX/Y information (e.g. Firefox) or tend to screw it up (e.g. Opera), but luckily they are the ones that provide (mostly) accurate pageX/Y coordinates, even in exotic layouts. So using that as a fallback, we end up with the following function, which works in every browser I've tried:
/**
* Retrieve the coordinates of the given event relative to the center
* of the widget.
*
* @param event
* A mouse-related DOM event.
* @param reference
* A DOM element whose position we want to transform the mouse coordinates to.
* @return
* A hash containing keys 'x' and 'y'.
*/
function = getRelativeCoordinates(event, reference) {
var x, y;
event = event || window.event;
var el = event.target || event.srcElement;
if (!window.opera && typeof event.offsetX != 'undefined') {
// Use offset coordinates and find common offsetParent
var pos = { x: event.offsetX, y: event.offsetY };
// Send the coordinates upwards through the offsetParent chain.
var e = el;
while (e) {
e.mouseX = pos.x;
e.mouseY = pos.y;
pos.x += e.offsetLeft;
pos.y += e.offsetTop;
e = e.offsetParent;
}
// Look for the coordinates starting from the reference element.
var e = reference;
var offset = { x: 0, y: 0 }
while (e) {
if (typeof e.mouseX != 'undefined') {
x = e.mouseX - offset.x;
y = e.mouseY - offset.y;
break;
}
offset.x += e.offsetLeft;
offset.y += e.offsetTop;
e = e.offsetParent;
}
// Reset stored coordinates
e = el;
while (e) {
e.mouseX = undefined;
e.mouseY = undefined;
e = e.offsetParent;
}
}
else {
// Use absolute coordinates
var pos = getAbsolutePosition(reference);
x = event.pageX - pos.x;
y = event.pageY - pos.y;
}
// Subtract distance to middle
return { x: x, y: y };
}Obviously if the elements you apply it to have an exotic positioning, it'll still go sour, but the above code at least improves the situation massively in Internet Explorer. Here's a little example.

In the example when you
In the example when you place the white box in the red square it doesn't show. Probably a z-index issue.
Drawing order
It works fine in browsers that respect the proper drawing order. I really can't be bothered fixing up such a silly example.
Geez Steve!
tell us how you really feel!!? LOL
The getAbsolutePosition()
The getAbsolutePosition() function works fine if your element is an Image ( <img> ) in a css layout. It is certainly suprising that the other elements do not give a correct position. Must be to do with the flow rendering.
colour information
Is it possible to obtain the colour information at the point we click the mouse?
Weee
Haha, I made a purely css and javascript scrubber for the flash chromeless video player using your function... sweet. Thanks man :)
Thank's!
Thank's mate!!!
It was exactly what I needed.
An admin tool to mark points on a map.
Thank's a lot!!!
Unfortunately, even this
Your getAbsolutePosition is not dealing with margins, borders, paddings... so - it is supposed to not to work well! Add border, margin or padding to your main DIV and all go wrong!
apply fix to farbtastic
Has this fix been applied to farbtastic? Reason I ask is because in IE7, when I bring up a farbtastic color wheel and click on it, it looks like the handler is calculating the position incorrectly (causing the crosshairs to not land where the pointer is clicked). Also I don't see the getRelativeCoordinates function in farbtastic.js 1.2.
re: apply fix to farbtastic
’pon further inspection I now see that fb.widgetCoords in farbtastic.js is essentially getRelativeCoordinates. Hum. Still have the IE7 problem though.
Try this
The fix listed here worked with an older version of jQuery. Try replacing fb.widgetCoords with the following (for jQuery 1.2.6):
fb.widgetCoords = function (event) {var offset = $(fb.wheel).offset();
return { x: (event.pageX - offset.left) - fb.width / 2, y: (event.pageY - offset.top) - fb.width / 2 };
};
cool style!!
I found your site googling for javascript mouse position and my eyes popped at your style -- it's fantastic!! and I don't think I've seen anything quite like it anywhere. Well done!!
Tim
Well.. It still sux big ass
Well.. It still sux big ass big time, that you cant get the position when using margin.. It totally ruins every chance of making a nice dropdownmenu on a scalable site without hardcoding it :( I just hat IE so much !
Post new comment