Not understanding fundamental layout concepts can have a great impact on your web design and implementation. For instance, depending on the requirements, a modal dialog can be rendered by calculating its size relative to the viewport and positioning it absolutely, or a simpler technique using fixed positioning and margins performs much better, and the likelihood of bugs is decreased because you are doing significantly less DOM manipulation via JavaScript.
The two key concepts for understanding how the browser lays out a page are normal flow and positioning.
The browser renders elements by placing them on a page according to what is known as normal flow.
Per the W3C CSS 2.1 specification:
Boxes in the normal flow belong to a formatting context, which may be block or inline, but not both simultaneously. Block-level boxes participate in a block formatting context. Inline-level boxes participate in an inline formatting context.
Let’s translate this W3C-speak into something someone other than a browser developer can understand.
“Boxes” refers to the box model, which describes how boxes represent elements. Every element on the page is a box composed of pixels. Boxes have properties such as padding, margins, and borders that, combined with the box content, determine how much space an element will occupy on a page.
In the normal flow, an element’s default position value is static. A static element is technically not positioned. A positioned element is said to have a position value of relative, absolute, or fixed, and it is removed from the normal flow. Positioning does not cascade (i.e., it is not inherited by child elements), so you have to specifically set it on an element. Otherwise, the default value of static will be applied.
When an element is positioned, it is considered to be a containing element. A containing element is a reference point for positioning child elements using the top, right, bottom, and/or left properties. This containing element then becomes the offsetParent for its child elements and any ancestors of the children that are not descendants of closer-positioned ancestors.
An element’s offsetParent property can be either null, the <body>, or an ancestor element other than the <body>. The browser uses the W3C specification to determine an element’s offsetParent value, as follows:
Occasionally, an element’s offsetParent value will be null. This occurs when the element is the <body>, when the element does not have a layout box (i.e., its display is none) and when the element has not been appended to the DOM. The offsetParent will also be null if the element’s position is fixed because it is positioned relative to the viewport, not another element. The following example illustrates these cases:
While the value of offsetParent can be null, the return value from jQuery’s offsetParent method is never null. jQuery returns <html> as the offsetParent, ensuring that there is always an element against which to operate.
If the element is not a descendant of a positioned element and it does not meet any of the null criteria, then its offsetParent will be the 'body'.
If the element is a descendant of a positioned element, then the closest positioned ancestor will be its offsetParent. If an element is not a descendant of a positioned element but is a descendant of <td>, <th>, or <table>, then its offsetParent will be the closest of the aforementioned tags.
Positioning is frequently misunderstood. Often developers will set a different position value on an element in an attempt to fix a layout when things are not looking quite right. While this approach can “fix” a layout, it does not promote an understanding of how positioning actually works. Having a firm grasp of positioning can save you a great deal of time in the long run, because once you understand how it works you will begin to construct pages in a much more efficient manner.
When an element is positioned relatively, the space it would have occupied in the normal flow is reserved. All other elements in the normal flow render around the space as if the element were in the normal flow. Visually, it is placed as if its position value were static. However, unlike with a static element,setting the top, right, bottom, or left values will shift the element accordingly. Positioning an element relatively is typically done to create a containing element for positioning child elements, or to apply a z-index (z-index is not applicable to statically positioned elements). Relative positioning is assigned as follows:
When an element is positioned absolutely, it is taken out of the normal flow of the document and the space the element would have occupied collapses. The element can then be positioned by setting the top, right, bottom, and left values. Absolute positioning is useful for creating components such as tooltips, overlays,or anything that should be positioned at a precise location outside of the normal flow.
An element is also taken out of the normal flow of the document when its position is fixed. It is positioned relative to the viewport, not the document, so when the page is scrolled the element remains fixed in place as if it were part of the viewport itself.
This is useful for creating sticky headers and footers, and modal overlays. Here’s an example:
In addition to understanding positioning, it is important to know how to get an element’s position relative to the viewport and the document. This is useful for positioning elements relative to each other when creating components such as tooltips, dialogs, sliders, etc.
Getting an element’s position relative to the viewport alone is typically not that useful. Most of the time you want the element’s position relative to the document, so you can absolutely position another element next to it.
However, the method used to retrieve the position relative to the viewport, element.getBoundingClientRect, returns the top, right, bottom, and left values, which allows you to easily calculate an element’s width and height. This information, in conjunction with an element’s position relative to the document, is useful when creating components that need to align with another element or be constrained by another element’s size, such as drop-downs or tooltips.
Alternatively, you could just use $.outerWidth and $.outerHeight to obtain an element’s dimensions, but element.getBoundingClientRect is significantly faster. Also, element.getBoundingClientRect can be used with a couple of other properties to quickly look up an element’s position relative to the document:
Getting an element’s position relative to the document is useful for absolutely positioning an element so that it remains positioned as the page scrolls, and for positioning elements relative to each other within the document. It is a fairly straightforward process. You traverse the DOM, finding each offsetParent, and get its offsetLeft and offsetTop. These values are the offset from the next offsetParent, so you have to sum the values until you reach the <body>:
A quick Internet search for “JavaScript get element position relative to the body” will result in numerous examples that are roughly the same as the one outlined here. However, there is a much better way of doing this in conjunction with element.getBoundingClientRect, courtesy of jQuery:
I recommend just using $.offset as it performs consistently across browsers, and jQuery has been well tested and vetted by a very large user community. $.offset is an excellent example of the benefits of using a DOM manipulation library: you get the collective intelligence of a community wrapped up in a nice API.