Introduction
Sometimes (especially in AJAX projects), it is necessary to get the position of some DOM element in "absolute" coordinates within the current document. For example, such an "absolute" position is needed if you would like to show some hidden DIV object exactly on the position (or with some offset) of another element. We use this function in one of our projects to show a popup menu under a text label.
The Solution
Such properties as style.left, style.top, or offsetLeft, offsetTop can be used to get (or set) the position of an element within its parent. So, to get the element's absolute position within the document, we should move upwards on the element's tree and add the position of all the element's parents (except the latest document element).
However, it is not quite easy. There are still some problems:
- First, we need to take into account possible scrolling in the element's parents and decrease our result accordingly.
- Second, there are some distinctions in the behavior of different browsers (as usual :-( ). For Internet Explorer, we always just subtract the scrolling position of the object stored in the element's
offsetParent property. But for FireFox, we also need to take into consideration all the parents accessible by the parentNode properties.
- Finally, we should take into account the border width for some parent elements. Unfortunately, this task is not so easy as it can be supposed, especially for Internet Explorer.
So, here is the function we get:
function __getIEVersion() {
var rv = -1; if (navigator.appName == 'Microsoft Internet Explorer') {
var ua = navigator.userAgent;
var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
if (re.exec(ua) != null)
rv = parseFloat(RegExp.$1);
}
return rv;
}
function __getOperaVersion() {
var rv = 0; if (window.opera) {
var sver = window.opera.version();
rv = parseFloat(sver);
}
return rv;
}
var __userAgent = navigator.userAgent;
var __isIE = navigator.appVersion.match(/MSIE/) != null;
var __IEVersion = __getIEVersion();
var __isIENew = __isIE && __IEVersion >= 8;
var __isIEOld = __isIE && !__isIENew;
var __isFireFox = __userAgent.match(/firefox/i) != null;
var __isFireFoxOld = __isFireFox && ((__userAgent.match(/firefox\/2./i) != null) ||
(__userAgent.match(/firefox\/1./i) != null));
var __isFireFoxNew = __isFireFox && !__isFireFoxOld;
var __isWebKit = navigator.appVersion.match(/WebKit/) != null;
var __isChrome = navigator.appVersion.match(/Chrome/) != null;
var __isOpera = window.opera != null;
var __operaVersion = __getOperaVersion();
var __isOperaOld = __isOpera && (__operaVersion < 10);
function __parseBorderWidth(width) {
var res = 0;
if (typeof(width) == "string" && width != null && width != "" ) {
var p = width.indexOf("px");
if (p >= 0) {
res = parseInt(width.substring(0, p));
}
else {
res = 1;
}
}
return res;
}
function __getBorderWidth(element) {
var res = new Object();
res.left = 0; res.top = 0; res.right = 0; res.bottom = 0;
if (window.getComputedStyle) {
var elStyle = window.getComputedStyle(element, null);
res.left = parseInt(elStyle.borderLeftWidth.slice(0, -2));
res.top = parseInt(elStyle.borderTopWidth.slice(0, -2));
res.right = parseInt(elStyle.borderRightWidth.slice(0, -2));
res.bottom = parseInt(elStyle.borderBottomWidth.slice(0, -2));
}
else {
res.left = __parseBorderWidth(element.style.borderLeftWidth);
res.top = __parseBorderWidth(element.style.borderTopWidth);
res.right = __parseBorderWidth(element.style.borderRightWidth);
res.bottom = __parseBorderWidth(element.style.borderBottomWidth);
}
return res;
}
function getElementAbsolutePos(element) {
var res = new Object();
res.x = 0; res.y = 0;
if (element !== null) {
if (element.getBoundingClientRect) {
var viewportElement = document.documentElement;
var box = element.getBoundingClientRect();
var scrollLeft = viewportElement.scrollLeft;
var scrollTop = viewportElement.scrollTop;
res.x = box.left + scrollLeft;
res.y = box.top + scrollTop;
}
else { res.x = element.offsetLeft;
res.y = element.offsetTop;
var parentNode = element.parentNode;
var borderWidth = null;
while (offsetParent != null) {
res.x += offsetParent.offsetLeft;
res.y += offsetParent.offsetTop;
var parentTagName =
offsetParent.tagName.toLowerCase();
if ((__isIEOld && parentTagName != "table") ||
((__isFireFoxNew || __isChrome) &&
parentTagName == "td")) {
borderWidth = kGetBorderWidth
(offsetParent);
res.x += borderWidth.left;
res.y += borderWidth.top;
}
if (offsetParent != document.body &&
offsetParent != document.documentElement) {
res.x -= offsetParent.scrollLeft;
res.y -= offsetParent.scrollTop;
}
if (!__isIE && !__isOperaOld || __isIENew) {
while (offsetParent != parentNode &&
parentNode !== null) {
res.x -= parentNode.scrollLeft;
res.y -= parentNode.scrollTop;
if (__isFireFoxOld || __isWebKit)
{
borderWidth =
kGetBorderWidth(parentNode);
res.x += borderWidth.left;
res.y += borderWidth.top;
}
parentNode = parentNode.parentNode;
}
}
parentNode = offsetParent.parentNode;
offsetParent = offsetParent.offsetParent;
}
}
}
return res;
}
To use this function, just pass your element in the function's parameter and get the result object with the left and top coordinates stored in the x and y properties accordingly:
var pos = getElementAbsolutePos(myElement);
window.alert("Element's left: " + pos.x + " and top: " + pos.y);
The getElementAbsolutePos function was tested on the most common browsers:
- Internet Explorer 6.0 and higher
- Firefox 2.x and higher
- Opera 9.x and higher
- Google Chrome
History
- 21st April, 2009: Initial post
- 28th April, 2009: Article updated
- 20th April, 2011: Article updated
Software developer and architect, entrepreneur.
Main kind of activity and interest:
- software development and promotion (.NET and Delphi components mainly, see
devtools.korzh.com for details);
- software engineering (extreme programming, design patterns, etc.);
- IT industry trends.
Main projects:
*
EQB - user-friendly query builder with natural language UI;
*
Localizer - localization tool kit for Delphi applications;