Let’s say we have a basic HTML layout: a container DIV and within it another DIV and an image:
<div id="xdivContainer"> <div id="xdivMenu" style="position:absolute"></div> <img id="ximgIcon" src="icon.gif" /> </div>
Inner (child) div is positioned absolutely and we want to place it at the coordinates of the image (think popup menu appearing on icon click). A very basic JavaScript function obtains position of the image:
// Class placeholder for coordinates function CTopLeft(i_nTop, i_nLeft) { this.nTop = i_nTop; this.nLeft = i_nLeft; } // obtain position of a DOM element function GetTopLeft(i_oElem) { var cTL = new CTopLeft(0, 0); var oElem = i_oElem; while (oElem) { cTL.nLeft += oElem.offsetLeft; cTL.nTop += oElem.offsetTop; oElem = oElem.offsetParent; } return cTL; }
As you can see it simple collects offset positions of the HTML element, combining them into absolute coordinates. So to absolutely position our child DIV at the coordinates of the image we can call it like this (I am using $()
notation as a shortcut for document.getElementById
)
var oPos = GetTopLeft($('ximgIcon')); $('xdivMenu').style.top = oPos.nTop + 'px'; $('xdivMenu').style.left = oPos.nLeft + 'px'
And it works fine. Until container DIV is in turn absolutely positioned:
<div id="xdivContainer" style="position:absolute"> <div id="xdivMenu" style="position:absolute"></div> <img id="ximgIcon" src="icon.gif" /> </div>
This small change leads to huge consequences. It resets absolute coordinates for all children of container DIV to itself, so (top=0, left=0) no longer represent coordinates within window, but within DIV instead. So if we call function GetTopLeft
as is, suddenly you will find your child DIV positioned far below and to the right of the image.
The solution is to slightly modify GetTopLeft
function to take into account possibilities of elements with absolute position:
// obtain position of a DOM element function GetTopLeft(i_oElem) { var cTL = new CTopLeft(0, 0); var oElem = i_oElem; while (oElem && oElem.style.position != 'absolute') { cTL.nLeft += oElem.offsetLeft; cTL.nTop += oElem.offsetTop; oElem = oElem.offsetParent; } return cTL; }
It’s almost identical to the one above with one difference: it stops collecting offset positions as soon as it encounters parent with absolute positioning. When it does encounter such element it exits providing absolute coordinates within it, and positioning of our child DIV via style.top
and style.left
in the call above will work correctly now.
UPDATE 3/1/2013: Turns out that if container element has position:relative
, it also similarly affects the calculations. Also, if element’s style is set via CSS class and not directly then style.position
will not return correct value, we need somehow to get current/computed style of the element. This can be done by the following cross-browser compatible function:
function getStyle(el, styleProp) { /// <summary> /// Returns *computed* (current) style of an element /// </summary> /// <param name="el">DOM Element</param> /// <param name="styleProp">Style property, e.g. "posistion"</param> /// <returns>Requested style</returns> if (el.currentStyle) var y = el.currentStyle[styleProp]; else if (window.getComputedStyle) var y = document.defaultView.getComputedStyle(el, null).getPropertyValue(styleProp); return y; }
and the main function will respectively change to
// obtain position of a DOM element function GetTopLeft(i_oElem) { var cTL = new CTopLeft(0, 0); var oElem = i_oElem; while (oElem && getStyle(oElem, 'position') != 'absolute' && getStyle(oElem, 'position') != 'relative') { cTL.nLeft += oElem.offsetLeft; cTL.nTop += oElem.offsetTop; oElem = oElem.offsetParent; } return cTL; }