function getViewOffset(node, singleFrame) {
function addOffset(node, coords, view) {
var p = node.offsetParent;
coords.x += node.offsetLeft - (p ? p.scrollLeft : 0);
coords.y += node.offsetTop - (p ? p.scrollTop : 0);
if (p) {
if (p.nodeType == 1) {
var parentStyle = view.getComputedStyle(p, '');
if (parentStyle.position != 'static') {
coords.x += parseInt(parentStyle.borderLeftWidth);
coords.y += parseInt(parentStyle.borderTopWidth);
if (p.localName == 'TABLE') {
coords.x += parseInt(parentStyle.paddingLeft);
coords.y += parseInt(parentStyle.paddingTop);
}
else if (p.localName == 'BODY') {
var style = view.getComputedStyle(node, '');
coords.x += parseInt(style.marginLeft);
coords.y += parseInt(style.marginTop);
}
}
else if (p.localName == 'BODY') {
coords.x += parseInt(parentStyle.borderLeftWidth);
coords.y += parseInt(parentStyle.borderTopWidth);
}
var parent = node.parentNode;
while (p != parent) {
coords.x -= parent.scrollLeft;
coords.y -= parent.scrollTop;
parent = parent.parentNode;
}
addOffset(p, coords, view);
}
}
else {
if (node.localName == 'BODY') {
var style = view.getComputedStyle(node, '');
coords.x += parseInt(style.borderLeftWidth);
coords.y += parseInt(style.borderTopWidth);
var htmlStyle = view.getComputedStyle(node.parentNode, '');
coords.x -= parseInt(htmlStyle.paddingLeft);
coords.y -= parseInt(htmlStyle.paddingTop);
}
if (node.scrollLeft)
coords.x += node.scrollLeft;
if (node.scrollTop)
coords.y += node.scrollTop;
var win = node.ownerDocument.defaultView;
if (win && (!singleFrame && win.frameElement))
addOffset(win.frameElement, coords, win);
}
}
var coords = { x: 0, y: 0 };
if (node)
addOffset(node, coords, node.ownerDocument.defaultView);
return coords;
}
(The optional second argument turns off recursing in parent frames, when set, so you get document-relative coordinates .)As I recently had a use for half of it -- computing Y positions -- I hacked out separate smaller getYOffset and getXOffset versions, and when I had those, it occurred to me that these ought to be properties in the
Element
DOM interface and implemented behind the curtains, so we could simply write img.documentX
, img.documentY
, et cetera, or img.pageX
and img.pageY
, if we wanted the coordinates of the image, counting from the outer(most) surrounding parent window. Hacking up a mini-library for that was a breeze from these primitives:function documentX() { return getXOffset(this, 1); }
function documentY() { return getYOffset(this, 1); }
function pageX() { return getXOffset(this); }
function pageY() { return getYOffset(this); }
Node.prototype.__defineGetter__('documentX', documentX);
Node.prototype.__defineGetter__('documentY', documentY);
Node.prototype.__defineGetter__('pageX', pageX);
Node.prototype.__defineGetter__('pageY', pageY);
So now you could write code looking like this to inspect coordinates of things hovered by the mouse:Hovered element: <input type="text" id="node-coords" /> <=
<input type="text" id="mouse-coords" />
<script src="http://ecmanaut.googlecode.com/svn/trunk/lib/getXOffset.js"></script>
<script src="http://ecmanaut.googlecode.com/svn/trunk/lib/getYOffset.js"></script>
<script>
var mouse = document.getElementById('mouse-coords');
var output = document.getElementById('node-coords');
var hovered = document.body, saved = hovered.style.outline || '';
hovered.addEventListener('mousemove', hovering, false);
function hovering(e) {
mouse.value = 'mouse @ '+ e.pageX + ', '+ e.pageY;
var node = e.target;
if (node === hovered) return;
var what = node.tagName +' @ ';
var where = node.pageX +', '+ node.pageY
output.value = what + where;
hovered.style.outline = saved;
saved = (hovered = node).style.outline;
hovered.style.outline = '1px dashed lightBlue';
}
</script>
Hovered element: <= Until this kind of ease gets into DOM 3 or 4 (we can hope, at least), your code is better off using getViewOffset instead, though, when you wanted both properties anyway:
// ...
var coords = getViewOffset(node);
var where = coords.x + ', '+ coords.y;
// ...