Position absolute is really relative?

From Jimbojw.com

Jump to: navigation, search
Tip: This is part of the Weekly Web Hack article series. If you like it, please subscribe to my blog. Thanks!

In this edition of the Weekly Web Hack, I'll explain the difference between the four possible values for the 'position' CSS attribute, focusing on the meaning of position:absolute.

By the end of this article, you should be able to:

  1. Understand what static, relative, absolute and fixed positioning are,
  2. Subvert the rules of 'position:absolute' to achieve proper element placement.

Position options

There are four possible values for the 'position' attribute, described briefly below:

static

This is the default. When an element has position:static, it will exist within the regular "flow" of the page. Elements with position:static may have values for their 'top' and 'left' positions, but they will be ignored.

relative

When an element has position:relative, then the 'top' and 'left' values represents offsets from where the element would otherwise normally appear.

For example, if an element had position:relative and top:-10px, then it would appear on the page shifted up 10 pixels towards the top of the page from where it would normally appear.

fixed

An element which has position:fixed will remain at the same location, defined by its 'top' and 'left' attributes, relative to the viewable portion of the page. That is, if the user scrolls the page, any elements with position:fixed should remain in the same place with respect to the boundaries of the viewport.

Note: Versions of Internet Explorer prior to 7.0 do not implement position:fixed, so to achieve this effect requires JavaScript or browser specific CSS magic which is outside the scope of this article.

absolute

A common mistake made by new HTML/CSS/JS developers is to assume that position:absolute means that the element will be positioned absolutely with respect to the document body. That is, that such an element will appear a static distance from the top and left of "the page" regardless of scrolling. This is only half true. An element with position:absolute will be positioned according to the coordinates specified in its 'top' and 'left' attributes, relative to the nearest positioned ancestor.

Let's break that down a bit. An ancestor of an HTML element is any element upwards in the DOM tree. So in this example:
<ol><li><span>hello!</span></li></ol>
Both the <ol> and <li> elements are both "ancestors" of the <span>. Because they both have no position attribute specified, the default of position:static is assumed, and therefore they are not considered to be "positioned".

Consider though that they are both positioned - say they both had "position:relative". In that case, the <span> element's nearest positioned ancestor would be the <li> since it's nearer than it's parent, the <ol>.

The Problem

One problem with the above is the case where it is desired to take an HTML element and place it at a known coordinate relative to the page. This can be achieved by some gentle DOM manipulation. Consider this HTML fragment:

<div id="wrapper" style="width:600px;position:absolute;top:0;left:auto">
  Main wrapped content.
  <div id="popup" style="display:none">
    Some popup content.
  </div>
</div>
<div

As you can see, the "wrapper" div has a static 600 pixel width, with an automatically calculated 'left' attribute, and a 0 pixel 'top' offset. Let's say we want to display the "popup" div at a particular exact location relative to the page. Consider this snippet:

var popup = document.getElementById('popup');
popup.style.position = 'absolute';
popup.style.top = '100px';
popup.style.left = '100px';

The problem here is that since the popup div has a positioned ancestor (the wrapper div), it will not be placed at the 100x100 pixel location as expected.

The workaround is to reassign the popup div to the document body so that it's nearest positioned ancestor will be the document body itself. Here is the updated snippet:

var popup = document.getElementById('popup');
popup.style.position = 'absolute';
popup.style.top = '100px';
popup.style.left = '100px';
// Note: this will automatically detach popup from the "wrapper" div and reassign it to the document body
document.body.appendChild(popup);

And that's it! Really, that's all it takes. I write this article in hopes that it helps someone else. Not understanding the nature of position:absolute and the necessity of reassigning members to the document body cost me a good chunk of time on an otherwise simple task recently.

Good luck! As always I'll be happy to answer any questions.


Comments

Ali said ...

What about accessibility for non-visual users? If you attach the popup to the body, it is not going to be in the right DOM order and non-visual users can not easily find it.

--Ali 15:21, 27 November 2007 (MST)

Jimbojw said ...

Hi Ali,

That's a good point that I hadn't considered. Accessibility is important, and I'm a big fan of keeping content in the correct semantic order (see Absolutely relative HTML elements for an example).

Handling dynamically created content is something I haven't thought about much from an accessibility standpoint. One solution would be to calculate backwards up the DOM stack all of the effective 'position' attributes, get their 'absolute' offsets in order to determine where to place the div relative to the nearest positioned parent.

The way I came across this problem in the first place was a divergence in the way IE 6 renders position:absolute children from IE 7, FireFox, Konqueror and Opera. So all the work proposed in calculating a real offset may be for only a small gain.

I would suggest that if accessibility of the popup is important - that is, that it really is important information and not just junk - and backwards compatibility with IE6 is required, then the web developer should avoid having children of positioned elements at all so that this doesn't become a problem.

I'm not aware of a simple solution that achieves cross-browser compatibility and also allows complex variants of positioned parent elements, however if there is one, I'd certainly like to know about it!

--Jimbojw 17:29, 27 November 2007 (MST)

Michael M said ...

I found it strange that the page flow at and around to the original location of a relatively positioned object was not affected by the absense of the moved object.

Your article made it possible for me to make the area "appear uninhabited"...as it should when the object has been movied...by declaring the parent object as position:relative and the inner object as position:absolute with left and top properties assigned as normal.

Thank you!

--Michael M 15:10, 23 December 2007 (MST)



Got something to say?

Leave a comment
Sorry, comments are disabled.

or, read what others have said...