20 Jan 2018
In a previous post we studied some major components of the browser, including the rendering engine. In this post we’ll dig a bit further on the layout of the render tree and take a look at an important piece of this process: the cascading style sheets or CSS.
To recap, every DOM element is usually represented by one rectangle. The job of the rendering engine is to determine two properties of these rectangles: their size (height, width), position (top, left) and stacking order when they overlap. In this post we’ll see how different CSS properties can affect the resulting rectangle.
Note: we’ll use box and rectangle interchangeably in this post.
CSS stands for cascading style sheets. It was proposed by Håkon Wium Lie in 1994.
The CSS 1 specification was finished in 1996. CSS 2 was created to address some issues with the previous version in 1997. CSS 3 was started in 1998 but hasn’t been concluded yet! The thing is that CSS 3 is subdivided in modules and each is fairly independent of each other, which resulted in different modules having different phases.
The diagram below provides a good overview on the different modules from CSS and their stage:
Wikipedia has an interesting history of the development of CSS, including the initial lack of compliance to the specification which caused a lot of headaches to front-end developers, especially when working with early versions of the Internet Explorer.
It’s interesting to take a look at the early days of CSS because it plays a big role in how it looks today (due to back-compatibility). It’s useful to remember that back in the days when CSS first came around, web pages were generally pure HTML containing mostly text, so a lot of CSS was designed around concepts such as paragraphs and simple images.
In this post we’ll focus on the CSS modules that affect the layout of the DOM elements, in particular the Visual formatting model. According to the spec, these are factors that influence the layout of a box:
We’ll briefly cover some of these properties and then play with a few examples.
The display property
There are many possible values for the display property. The main ones are
inline-block. I’ve been using
flex increasingly but that deserves a post in itself (this article is a great reference).
none removes the element from the layout calculation so it’s effectively invisible.
A value of
block causes the element to be visually formatted as a block  (other values like
table do to). In general a block is a box that starts at a new line and takes the entire width of the parent.
inline box on the other hand starts from the left of the previous box. It also ignores explicit
height values and any explicit vertical spacing (i.e.
The main difference between
inline-box is that the latter does account for
height, and vertical spacing .
The position property
There are 5 possible values for the
static is the default positioning schema and it follows the normal positioning flow of the page.
relative positioned element accounts for top and left properties. These are in relation to the parent.
absolute positioned element is similar to a relative, except that it is removed from the normal layout flow (i.e. other elements ignore its existence when being positioned) and its top and left are in relation to the first positioned ancestor in the DOM tree (or the document if none is). A positioned element is any with
Here is an example where we only change the inner (red) div to
In the first example,
A ignores the top/left properties. In the third example is “crosses” the boundary of the middle (blue) box because it’s not positioned.
Note that in the code above we have set
auto. This is a hack to prevent margin-collapsing.
An element with
position:fixed is similar to
position:absolute, except that instead of having its offset relative to an ancestor with
position:relative, usually it’s relative to the viewport. That implies that such element scrolls with the page. The special case happens when one of its ancestors has
filter set to something other than
none, in which case it behaves much like an absolute positioned element.
Finally, as described in  an element with
position:sticky is treated as relatively positioned until it crosses a specified threshold, at which point it is treated as fixed until it reaches the boundary of its parent.
The float property
When a element is floated, it is taken out of the normal layout flow of the document. It is shifted to the left (assuming a
float:left) until it touches the edge of its containing box, or another floated element.
float implies the use of the block layout, it modifies the computed value of most of the
display values to block.
Floats are better understood in relationship with other types of boxes. Let’s check some examples.
Float + Block
The green block ignores the presence of the blue block but its child (yellow) does not. That’s because
yellow are in different block formatting contexts.
Float + Inline
Analogous to a block context, an inline formatting context is a set of inline elements. In such context, the boxes are laid out horizontally. Each “row” of inline elements is called a line box. In the presence of floats, the spec states the following:
In general, the left edge of a line box touches the left edge of its containing block and the right edge touches the right edge of its containing block. However, floating boxes may come between the containing block edge and the line box edge. Thus, although line boxes in the same inline formatting context generally have the same width (that of the containing block), they may vary in width if available horizontal space is reduced due to floats. We can see an example of that in here. In this example the first three line boxes have a shorter width than the fourth one due to the presence of the float element.
When applied to a floating element,
clear moves the margin edge of the element below the margin edge of all relevant floats (depending on whether it’s clear
both). Here’s an example:
The z-index property
Besides determining the size and position of boxes, the layout engine needs to determine how to handle overlapping. The boxes ordering is transitive, meaning that if a box A is under B, and B is under C, A has to be under C.
The main attribute to control the stack order of elements is the
z-index property (in reference of the z-axis, commonly used as the “depth” dimension in 3D). But this number only applies for boxes under the same class. As we’ll see now, it’s more complicated than it seems.
First, we need to define the concept of stacking context. A stacking context encompasses a set of DOM elements which can be compared to each other. The order of the stack context always take precedence over individual orders of elements within a stack context. For example, imagine that we have 2 stacking contexts:
Stack context 1:
[A -> B -> C -> D]
Stack context 2:
[E -> F -> G]
Stack context 1
-> Stack context 2
The arrow (
->) represents that the element on the left is on top of the element on the right. Because A, B, C and D belong to context 1, all of them are placed over E, F and G, no matter how big the z-index of elements in the second group are.
Another way to see it is that a stacking context defines an “atomic position”, meaning that elements from outside it cannot be placed in between its elements. It has to be either above or below.
In the DOM tree, if a node E satisfies some conditions, it starts a new stack context, which means that all elements in the DOM subtree of that element will be under that stack context (we say E is the root of such stacking context). Note that a stack context can contain other stack contexts. A few properties that cause a stacking context to be formed are:
relativeand z-index other than auto
opacityless than 1.
position: static ignores z-indexes, so a corollary is that the use of
z-index effectively creates a new stacking context.
Within a stacking context, the order of elements is defined by the spec. Here’s a simplified version, from bottom to top:
Another corollary is that a parent is always rendered below its children due to the first rule above.
Here is an example with elements of each of these categories.
Here is an interesting example with stacking contexts.
The red box is the parent, so it has to be under the green box. The green, purple and black boxes are in the same stacking-context so they are ordered based on the z-indexes (since red doesn’t start a stack context, green belongs to the top-level stack context). Finally, note how pink has the highest z-index, but is still placed under green because it belongs to the stack context of purple, which is placed under green.
My main goal with this study was to learn more about the layout algorithm used by render engines but this turned out to be an analysis of some CSS properties. It was an interesting exercise anyway. I work with CSS on a daily basis but often times I don’t take the time to read the spec or understanding why a given set of properties behave in a certain way.