Position:Absolute within Position:Absolute

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;
}

Leave a Reply

Your email address will not be published. Required fields are marked *