donderdag 16 september 2010

More about the z-algorithm and stacking contexts

In this post I explained how the concept of stacking context works and how it is useful to DomScape. I ended the post with a pseudo-algorithm to produce z-values on basis of the stacking context. I felt the need to explain it a bit more elaborately. But first a small introduction into HTML and stacking contexts.


HTML is a markup language that contains over 100 markup tags, also called HTML elements, that allow the web page author to describe the content of a web page. HTML is hierarchical: elements can (and will) contain other elements. The elements highest in the hierarchy often describe general sections on the website: a menu, a header, content section, etc. The elements that are deeper in the hierarchy often describe a particular section in more detail, e.g. a menu item.

Because of this hierarchical structure elements that are high in the HTML element hierarchy often have many descendants. These elements all render in the same location unless they are specifically told not to. It is easy to see that a web page of almost any size will thus contain a fair amount of overlap occurring between ancestors and descendants.

Elements can be also be displaced by applying certain CSS layout properties. This can make elements overlap that are not directly related to each other. HTML deals with these kind of situations by employing the stacking context. All elements in a web page are in a stacking context. When two elements overlap the stacking context is used to determine what element is rendered on top.

A stacking context consists of seven layers that each contains a different kind of elements. Every element in a web page belongs to one of these layers. Starting from back to front, the seven layers are rendered on top of each other. Elements in layer n always render on top elements in layer n - 1.

  1. The background and borders of the element that establishes the stacking context
  2. Positioned descendants with negative z-index
  3. Block-level descendants in the normal flow
  4. Floating descendants and their contents
  5. Inline-level descendants in the normal flow
  6. Positioned descendants with z-index set to auto or 0
  7. Positioned descendants with z-index greater than 0
I will not go into detail what every layer means as this is covered here.

Important to note is that by default the stacking context is established by the highest element in the HTML hierarchy, the body element. If, however, an element is positioned, i.e. if the element is in layer 2, 6 or 7, it will establish a stacking context for all of its descendants. A web page can - and often will - contain more than one stacking context.

Algorithm
In DomScape all elements need a value on the z-axis in order to generate a 3D perspective, but how do we do that? In recent posts I formed two important constraints for the solution:

  1. Elements cannot overlap in 3D space. All elements should be easily discernible from other elements. If elements occupy the same space on the x,y-plane they should have different z-values.
  2. The 3D perspective should look exactly like the normal, 2D perspective when viewed orthogonally from the top. Elements that are rendered on top in a web page thus have higher z-values than elements that render at the bottom.
Considering the second point it becomes very obvious why the stacking context is so relevant to DomScape: it provides the correct order of elements. Although it does not give exact z-values, the order of the elements can be translated into z-values. That is what the following algorithm does.

e, f: element

E: collection of all elements  
Ei: collection of all elements in the initial stacking context
F: collection of elements with calculated z

assign_z_to Ei

function assign_z_to: context C
 source_sort C
 z-index_sort C
 layer_sort C

 for every element e in C:
   z of e = 0
    
for every element f in F:
  if e overlaps with f
       e.z = 1 + f.z

   add e to F
   if e establishes context C'
     assign_z_to C'     

In human language the algorithm does the following. It takes all elements in the initial stacking context, established by the body element, and sorts it in three ways so that the order of the elements represents the order of the stacking context. All elements that belong to this stacking context are sorted on their stacking layer. If the element is in layer two or seven, the elements are sorted by z-index as well. When elements are in the same stacking layer and have the same z-index (or no z-index), those elements are sorted by their order in the source code.

The algorithm then iterates through the sorted elements - starting at the element that is lowest in the stacking context. If an element overlaps with an earlier element, the element is given a higher z-value, because a later element should be rendered on top of an earlier element. Earlier elements - with a z-value assigned - are kept in a separate list to speed up the algorithm.

Whenever an element is in layer 2, 6 or 7 it establishes a stacking context for its descendants. The z-values for this new context are calculated first, because the next element in the current context might overlap with the elements of the new context.

I have now implemented this algorithm in my working prototype. It produces 3D models that meet both constraints I mentioned earlier. The following video shows a web snippet with all elements in level 3: Block-level descendants in the normal flow. In future videos I will show how the algorithm provides different z-values when the elements are in different layers.

woensdag 15 september 2010

Implementation process underway

It has been a little over two months since my last blog post on the progress of DomScape, also known as my thesis. During the Summer months it’s always hard to get a lot of stuff done, especially if you spent most of your time in the Netherlands with friends you haven’t seen in a long time. Suffice to say, I didn’t do anything. However, I have managed to pick up where I left it and there has been great progress since.

Where I left you
I ended my last blog post with an algorithm - in pseudo-code - that assigns all elements in any web page a value on the (unused) z-axis. The algorithm does this by analyzing the stacking context of the web page, which determines how overlapping elements should be rendered - in web pages most elements overlap one or more other elements. A web page is rendered in 2D, but the order of elements can be used to explode this 2D view into a 3D view.

In the following mock up video the benefit of the 3D view is demonstrated. Two identically looking web pages are shown in 3D. The first web pages is generated with clean HTML: every element has a function, there is no redundancy. The second example looks identical to the first example, but it is generated with some HTML elements that have no apparent function in the rendering of the 2D view.





The question arises to what extent this is useful to the web developer. For example, aren’t there already tools such as Firebug, that allows a web developer to analyze a web page? What is the benefit of a 3D view? The answer is that by adding an extra dimension, the visualization is much more revealing. It is not hard to detect the redundant blue element in the second example of the video. How long would it have taken to do this by using Firebug?
Elements that are overlapped and hidden from sight in the 2D view become visible in 3D. In fact, all elements in the web page can be distinguished from each other, because they are assigned a unique position in 3D space. To further reveal why a web page looks the way it does, the properties of each element and its relationship to other elements can be visualized. This allows the web developer the analyze multiple elements simultaneously and interpret structure quickly and accurately.
Creating a 3D environment
With the help from my friend Alejandro Valenzuela I have managed to create a OpenGL implementation of DomScape. It is written in C++ and uses Alejandro’s 3D engine called MotorJ to take care of the low-level implementation. The development is an ongoing process, but I have managed to implement the following so far:
  • 3D rendering of basic elements
  • orbiting camera
  • Loading of models through XML parser
  • Generating z-values according to the stacking context
The following video shows how the implementation currently looks. I have taken the example web snippet I have been using on this blog as an example and loaded it in DomScape.




The work to be done
This video is the first glimpse of the DomScape and logically there is a lot of work to be done. There are a number of small issues that will be ironed out, hopefully next week. When the implementation gets to the level where it looks like the first mock up, I will start focusing on the interface. It is interesting to see how such a 3D view can be extended with visual guidelines and indications to help the web developer understand what he is seeing. This is the second question of my thesis.

I intend to do user testing to see how people web developer experience DomScape. However, I have constructed the following list of properties that seem important for a web developer in order to understand the positioning of elements:
  • properties of an element
    • height, width
    • margin
    • padding
    • background color
  • relationship between elements
    • parent - child
    • siblings
  • stacking context
    • layer
    • context
I will start focusing more on the actual interface of DomScape once I am happy with the capabilities of the current implementation. It is funny that it has taken me quite a while to get a solid foundation upon which I can start working on the interface. First, I wanted to prove that a 3D visualization is possible. I did this before holidays started. Now I am working on getting the basic implementation ready for action. I can’t wait to start with the cool stuff - interaction design.

woensdag 14 juli 2010

Stacking solves stuff

In my thesis I investigate the possibility of developing a tool that visualizes any web page as a 3D model - It is called DomScape. If you don't know what I am talking about, here's a video of the first mock up.


While writing a new post about the position property and how and when it causes overlap, I ran into a very important piece of CSS specification that I had never heard of. 'Stacking context' defines in what order elements should be rendered above one another. The stacking context is intertwined with other new concepts that I introduce in this post: stacking order, source order, z-index and stacking layers.

In this post I will explain how stacking context works and how it deals with overlap. I will then take this concept and put it into perspective of DomScape. I finish with a pseudo-algorithm that calculates z-values using stacking contexts.

What is stacking?
Stacking is used in CSS to determine what happens when elements overlap, i.e. occupy the same x,y-space. If we look at a group of overlapping elements, one of the elements is rendered on top and other elements are rendered below. We could say that there is a certain stacking order - the element that is rendered on top is the highest in this order and the element that is rendered at the bottom has the lowest position in the stacking order.

In my last post I looked at overlapping elements in normal flow. When elements overlap in normal flow, the order in the source code determines the stacking order. The element that occurs later in the code is rendered on top.

This figure demonstrates that source order determines the stacking order in overlapping elements in normal flow. Elements that occur later in the code are rendered on top of elements that occur earlier. 

As it turns out, stacking in normal flow is just a special case of stacking in general. CSS properties, such as float or position, also determine an element's position in the stacking order. The z-index property which can be set for elements that are not in normal flow (i.e. have a position other than static) can be used to determine the stacking order with greater control. If two elements overlap, the element with the highest z-index will be rendered on top.

Stacking layers

The CSS 2.1 specification defines seven layers that determine the stacking order of a group of elements that belong together. Elements are grouped because they all descent from the same stacking context. By default the stacking context is established by the highest element in the HTML hierarchy, the body element. As we will see later, any element can be made to establish a stacking context for its descendants.

Starting from back to front, there are seven layers. Every element belongs to either of these layers.
  1. The background and borders of the element that establishes the stacking context
  2. Positioned descendants with negative z-index
  3. Block-level descendants in the normal flow
  4. Floating descendants and their contents
  5. Inline-level descendants in the normal flow
  6. Positioned descendants with z-index set to auto or 0
  7. Positioned descendants with z-index greater than 0
This list can be quite overwhelming, so let's take a good look at it to see what it all means. 

Breaking it down
The third and fifth layer contain all the normally flowed elements. All elements in the negative margin example from my previous post exist within that layer. So, if two elements overlap and are in the same layer, then the source order determines the stacking order of those elements.

This example from my previous post demonstrates that when all elements are within the same layer, source order determines stacking order.

Because in the last example the text of the elements never overlap, I never noticed that inline-level elements always render on top of block-level elements. Although both types of element are part of the normal flow, block-level elements are rendered in the third layer and inline-level elements in the fifth layer.

The following example illustrates the two different ways of stacking (layer order vs source order). On the right an element is repositioned using negative margin. This causes overlap within two different layers and between those two layers as well. On the third layer the block-level elements overlap and on the fifth layer the two inline elements overlap.

Three times overlap: once between two layers and twice within layers.

Between the third and the fifth layer, the fourth layer is reserved for floated elements and their contents. If an element were floated in such a way that it would overlap with a displaced, but normally flowed, element, the floated element is always rendered on top, because it is in a higher layer. The following figure demonstrates that the elements are rendered differently when floating is involved.

Example of float element rendered over a block-level element. 

It is clear that the inline-level element wraps to the next line in order to avoid overlapping the floated element. However, if the elements are forced to overlap, the inline text will render on top of the floated element. This is a somewhat unexpected - but correct - behavior, because the floated element is rendered between a parent and child element, i.e. the yellow block-level element and the inline-level text element respectively.

Example of a float element rendered on top of a block-level element, but under the inline-level that is contained by that block-level element.


It is not necessarily the case that inline-level elements always overlap floated elements. If we take a good look at the layer 4 description, it explicitly mentions that not only floated elements are in that layer, the contents of the floated element are as well. Thus, when we let two floated elements overlap, both elements and their contents are in the same layer. At that point, source order determines stack order, as we have seen before.

When two floated elements overlap, source order determines which element is rendered on top.

It is important to note that 'contents' does not mean any kind of element. For example, if we declare a 'contents' element to be in layer six, that element is taken out of the fourth layer and rendered in the sixth layer, on top of the floated elements and (the rest of) the contents.

When two floated elements overlap, they and their contents are rendered in the fourth layer. If an element (in this case the inline-level 'I am floating' text) is made to render in a higher layer, it overlaps the floated elements.

Layer two, six and seven are reserved for elements that are not in normal flow, i.e. elements that have position set to either relative, absolute or fixed. For these elements the stacking order is determined by the z-index of each element. It is important to note here that most browsers reverse the first and second rule. This means that positioned elements with negative z-index are (incorrectly) rendered behind the stacking context's borders and background. Many web developers (unknowingly) abuse this bug to hide elements - in stead of using the display property. When a browser correctly implements the CSS specification, a element with a negative z-index is only completely hidden when an element with a higher z-index completely overlaps it.

Nested contexts
Layer one forms the absolute bottom of the stacking order. By default, the body element establishes the stacking context of the rest of the elements. However, there is often more than one stacking context in one web page. When an element has a z-index other than auto or 0, that element automatically establishes a local stacking context for all of its descendants. Only positioned elements can form new stacking contexts because, as we saw earlier, the z-index property can only be applied to positioned elements. In short, all elements in layer two and seven form new stacking contexts for their descendants.

Every element belongs to a stacking context. Most elements belong only to one stacking context, but when an element also establishes a new stacking context, to what stacking context does the element belong? In the original stacking context the element is either in the second or seventh layer, but it the self-established stacking context it (solely) occupies layer one.

Stacking order
Every stacking context has its own stacking order, which is relative to the element that established the stacking context. One interesting consequence of this is that an element can never 'escape' its stacking context: it is confined to the stacking level of the element that established the stacking context.

For example, imagine two elements, A and B, that are both absolutely positioned, overlap and are within the same stacking context. A is rendered on top B, because A has a higher z-index than B. As both elements have a z-index, they establish stacking contexts for their descendants. B contains a element, B', that is absolutely positioned with a z-index higher than 0. Because B' is confined to the stacking level of its stacking context (B), and A is rendered on top of B, B' cannot render on top of A - no matter what z-index we assign to B'.


A is rendered on top of B, because A has a higher z-index. A and B both establish new stacking contexts, thus B' is rendered inside its stacking context and will never overlap A or A' - no matter what z-index we assign to B'.

Visualizing stacking order
Stacking order determines which elements render on top. It is thus quite obvious that the stacking order is an important factor in determining the z-value for each element. In order to assign z-values to the elements in line with the stacking order, we need to know the following about each element:
  • x,y-space it occupies
  • its stacking context
  • its stacking layer
  • its z-index
  • its position in the source code
With this information we can sort the elements in such a way that all have an unique z-value and are in the same order as the stacking order. The following steps are executed:
  1. Take all elements that are in the first stacking context
  2. Sort those elements by the stacking layer, starting with the first layer
  3. If the layer is two, six or seven, sort elements that belong to the same layer by their z-index, starting with the lowest z-index
  4. Sort elements that have the same z-index by their position in the source code, starting with the earliest occurring element (even if it the z-index is not defined).
  5. If an element establishes a new stacking context, do these four steps again for that stacking context.
In the following example, the gray background establishes the stacking context and therefore has the lowest z-value. A and B are both in the sixth layer. A has a higher z-index than B, thus A has a higher z-value. Because B establishes a new stacking context for B', A must also have a higher z-value than B'. A' has a higher z-value than A, because A establishes the new context for A'.


Now that we know the order in which the elements are, it is easy to calculate the z-values for the elements: background: 0, A: 3, A': 4, B: 1, B': 2. This gives every element an unique position in x,y,z-space. The elements are also in the right order, which is necessary to render the web page correctly and to achieve a smooth transition between the 2D and 3D view. However, every z-value only contains one element. This is not very usable, because elements are probably too far apart to properly understand the context of the elements. We thus have to collapse the 3D model so that it becomes more compact and usable.

We can achieve this by allowing elements to have the same z-value as long as they don't occupy the same x,y-space. By doing this we allow elements from different layers and even different contexts to have the same z-value. If we take a look at the same example, we arrive at different - and not very surprisingly, lower - z-values: background: 0, A: 1, A': 2, B: 1, B': 2.

Z-algorithm
We can try to formalize the z-value calculation. The following pseudo-algorithm calculates the z-values for all elements according to the stacking context. I took the algorithm from my last post and adjusted it to use the stacking context to calculate the z-values.

e, f: element

E: collection of all elements  
C: collection of all elements in the initial stacking context
F: collection of elements with calculated z

source_sort E
z-index_sort E
layer_sort E
context_sort E

assign_z_to C of E
 
function assign_z_to: context C
  for every element e in C:
    z of e = 0 
     
    for every element f in F:
      if e overlaps with f
        z of e = 1 + z of f

    add e to F
    if e establishes context C'
      assign_z_to C'     

Basically, the difference between the original algorithm and the new one are in two things.

First, the elements in the original algorithm were sorted only on source order. In this algorithm all elements are sorted on the stacking context they belong to. All elements that belong to the same context are then sorted by their stacking layer. If the element is in layer two or seven, the elements are sorted by z-index as well. Last but not least, if elements are in the same stacking context, in the same stacking layer and have the same z-index (or no z-index), sort those elements by their order in the source code.

Second, when an element establishes a new stacking context, the z-values for this context are calculated first, before continuing with the rest of the current stacking context. Because the elements in the new stacking context could be overlapped by later elements, it is important to know the z-values of the elements in the lower layers first.

Conclusion
The discovery of CSS stacking has been a real eye opener. I have been making web sites for the past ten years and I had never come across it before. While writing this post I realized that stacking contexts are the answer to my first research question: Is the DomScape visualization possible? I asked this question, because I had no idea how CSS determines how elements should be rendered when they overlap. I figured I had to go through every CSS positioning and determine myself how the elements interact. The fact that CSS has a very straightforward approach is definitely helpful: it helps me answer the first question with much more confidence and accuracy and it will buy me precious time that I can spend on the other two questions: 

  2. What is needed to implement DomScape?
  3. What are DomScape's usability challenges?
    More reading
    CSS Discuss: Overlapping and z-index
    Sitepoint: Stacking contexts 
    Sitepoint: z-index
    W3C: Elaborate description of Stacking Contexts

    vrijdag 18 juni 2010

    Overlap makes you think

    In my thesis I investigate the possibility of developing a tool that visualizes any web page as a 3D model. If you don't know what I am talking about, here's a video of the first mock up.


    In my last post I explained the basic concepts behind the box model and CSS positioning. Now we can build upon that knowledge and see how well it plays with DomScape. In this post I will explain what the problem is with overlap caused by negative margin. Through the analysis of the consequences of overlap I have come to a better understanding what DomScape is and what constraints it implies. I will show that we have to let go of the Matryoshka metaphor and that we need a new algorithm to calculate the z-values of the elements.

    Negative margin
    With the margin property it is possible to reposition an element, but a margin can also be set to a negative value. If the negative value is big enough the element is displayed outside the boundaries of the containing block. This is demonstrated in the next figure.

    The element breaks out of the boundaries of the containing block and is even be wider than it, because it has a negative left margin (from CSS: The Definitive Guide)

    When an element is repositioned, it is likely that it will overlap a neighboring element. Which of the  elements is displayed on top depends on the properties of the browser that renders the web page. "Browsers usually render elements in order from beginning to end, so a normal-flow element that comes later in the document can be expected to overwrite an earlier element, assuming the two end up overlapping," Meyer explains in CSS: The Definitive Guide. 

    Overlapping elements present a problem for DomScape, because so far it has assumed that every element stays within the boundaries of its containing block. This is clearly not the case for elements with a negative margin and the same could be said for elements that have a absolute or relative position or are set to overflow. The exact effect of these properties will be discussed in later posts - for now we can just simply say that they cause overlap.

    The overlap caused by negative margin forced me to rethink what exactly DomScape is and how it could deal with overlap. What follows now is the result of my attempt to formalize DomScape and the challenges it faces.

    Constraints
    DomScape visualizes a web page as a 3D object by exploding the HTML elements over the z-axis. Elements keep their position on the x,y-plane and DomScape assigns a z-value to each element. DomScape needs an algorithm that automatically computes a z-value for each element, while also honoring certain constraints at the same time. What kind of constraints are we talking about?

    Discernible elements
    Every element in a web page occupies a certain x,y-space. Because a child element is confined to the x,y-space of its parent (unless it has been displaced), it overlaps its parent to some extent. Most elements will thus share their space with other elements. Overlapping elements can be hard hard, if not impossible, to visually distinguish. DomScape makes elements discernible by making sure that elements cannot overlap in the x,y,z-space. When elements occupy the same x,y-space, they'll need different z-values.

    Keep elements in context
    DomScape is a tool that gives the user more insight into why a web page looks the way it does. The HTML structure has a big influence on the layout, because it not only defines what elements exist in the web page, but also through the element hierarchy how elements relate to each other. DomScape must visualize the relationships that exists between elements to keep those elements in their respective contexts. The z-value of an element has of course a big effect on the perceived relationship it has with other elements. Elements that effect each other, e.g. parent and child elements, need to be close to each other. If the elements are unnecessarily far apart from each other, it would drastically lower the user's ability to understand why the layout looks the way it does.

    Smooth transition
    Another constraint is that the transformation between the 2D and 3D view is continuous. This is demonstrated in the video when the web page appears 2D, but is actually 3D when the user pans around it. What really happens is that the normal web page view is a 2D projection (from the top) of the 3D model. The 2D perspective is 'just another' (orthogonal) view on the the 3D model.

    Overlap
    From last point automatically follows that if two elements overlap on the x,y-plane, the element that is rendered on top in the 2D view is required to have the highest z-value. This is of course because the web page view is a 2D projection from the top.

    Overlap seems very straight-forward until you start considering the abnormal overlap that can occur through different kinds of CSS properties: negative margin, absolute and relative positioning and overflow. They can be used to displace an element so that it overlaps a neighboring element. An element that overlaps another element needs higher z-value than the element it overlaps, even when the overlapping occurs because one of the items is displaced. 

    Finding the right algorithm
    The reason I specifically mention overlap caused by element displacement, is because it excludes a certain algorithm which I had in my mind since the conception of DomScape. Inherent to the Matryoshka metaphor is that you don't have to look at the x,y-space the element occupies, but only at its position in the HTML hierarchy. The following figure shows that when we base the z-value of each element on its hierarchical position certain elements will have the same z value although they occupy the same x,y-space.

    Two rows of HTML elements with basic layout and the z-values in the bottom-left corners. The z-values are based on the position of the element in the hierarchy. This work fine in the first row when all elements stay within their parents boundaries, but violate the discernability constraint in the second row when elements are displaced.

    In the first row the only that occurs is by child elements overlapping their parents. The z-values are as you might aspect: none of the elements has a z-value higher than 3. In the second row the elements are displaced through negative margin and this creates a lot of overlap. Note that the fourth block (with the '... no margin' text) has not been displaced in any way by its margins: it's in the same position in both rows. However, because the fourth block occurs later in the code, it is rendered later and overwrites the third block.

    Displaced elements show us that we need to take in account the x,y-position of all elements. We know that if an element overlaps another element, the overlapping element must be at least one unit higher than that element. This guarantees that no element occupies the same x,y,z-space as any other element does and that the elements are in the right order. The following figure shows what happens when we calculate the z-values in this manner.

    Elements and their z-values in DomScape. In the first row the z-values are the same when using the hierarchical method. The z-values in the second row become quite high, but maintain the discernability constraint.

    This example demonstrates that in this approach high z-values can easily occur through overlap - even in items that haven't been explicitly made to overlap other elements. The following algorithm is used to compute z values for all elements in the figure above.

    z: z-value of an element  
    E: collection of all elements  
    F: collection of elements with calculated z

    for every element e in E:
      z of e = 0
      for every element f in F:
        if e overlaps with f
          z of e = 1 + z of f

      add e to F

    Context constraint
    This algorithm demonstrates how we can calculate the z-value of every element, but it doesn't solve the context issue of displaced elements. Because it is possible to displace an element by such an amount that it becomes unclear what the containing block of the element is, it looses its layout context. Well, of course the layout context won't have gone anywhere, but it will hard - if not impossible - for the user to figure that out without any visual cues. DomScape will have to incorporate a method of visually linking child elements to their parents to make it is easy for users to keep track of the DOM hierarchy and the layout context of each element.

    Vertical lines connect elements to help the user understand the layout context of each element. Such lines could also work very well when elements are displaced (from the DomScape concept video).

    In the concept video I have used vertical lines to help the user see where an element begins and ends compared to its parent. In general, we could say that the lines visualize the layout context of an element. In this example the lay out context is very straight-forward and easy to understand, but with displaced elements these lines can be very valuable in determining what the containing block of the element is, where the element would have been if it weren't displaced and how the element affects the positioning of its siblings. Future DomScape prototypes will test to what extent these lines are helpful and help maintain the context constraint.

    Conclusion
    Before I talked about certain problematic CSS properties, mainly relative and absolute positioning, float and overflow. I have shown that the overlap caused by margins can be properly visualized by using a different algorithm to calculate z-values. In future posts I will look at the alternative CSS positioning schemes to see if they can be approached in the same way as I have done in this post.

    Basics of CSS positioning

    In my thesis I investigate the possibility of developing a tool that visualizes any web page as a 3D model. If you don't know what I am talking about, here's a video of the first mock up.




    In my last post I took a dive into my first research question: Is the DomScape visualization possible? Apparently CSS positioning is a bit more complex than I first imagined, because I ended up more confused than I was before I wrote that post. I finished the post with the intent to research the CSS 2.1 Specification. Especially the parts that revolve around the positioning of elements are subject of my research: Section 8. Box model, Section 9. Visual formatting model and Section 10. Visual formatting model details. In theory those documents are all I need - in practice they are rather technical and very, very dry.

    Good thing I found CSS: The Definitive Guide by Eric Meyer, which is a very comprehensive and detailed book on CSS. It's a bit dated, but the concepts behind the CSS 2.1 specification haven't changed anyway. It basically goes through the entire specification to explain how CSS is meant to work and what the intricacies are. Because Meyer focuses a lot on edge cases his book is good way to find out where potential challenges lie for DomScape.

    Matryoshka metaphor
    In the last post I also introduced the concept of the Matryoshka or 'landscape' metaphor, which I use to indicate a certain property of the DomScape visualization. It means that the way DomScape makes a 3D representation of a 2D web page, is guaranteed to work if each element in the web page stays within the boundaries of its parent. There are several CSS properties that break the Matryoshka metaphor, because they do allow an element to extent outside the borders of its parent. As far as I currently understand, this is possible through these CSS properties: relative and absolute positioning, float and overflow

    The Matryoshka metaphor: elements stack upon each other without any overlap outside the boundaries of a lower element (taken from the DomScape concept video

    In order to understand how these special CSS positioning schemes work, it is important to get the basics out of the way first. I failed to do this in my last post and I have come to realize that it is hard to discuss advanced CSS positioning without first introducing the core principles of CSS positioning: the CSS box model, normal flow and the containing block.

    Box Model
    At the heart of CSS positioning lies the box model. Meyer explains the box model as follows, "CSS assumes that every [HTML] element generates one or more rectangular boxes, called element boxes. Each element box has a content area at its core. The content area is surrounded by optional amounts of padding, borders, and margins." In addition to this, the content area can contain any number of other elements/boxes. This creates an hierarchy of elements of which the root element is the browser's viewport.  


     Content area and its surroundings (from CSS: The Definitive Guide)



    There are two types of boxes, inline boxes and block boxes. Inline boxes are used to layout text and are generated by elements such as span, a, strong and em. In the default CSS positioning scheme, called normal flow, inline boxes are flowed (placed) horizontally - from left to right in Western languages. Inline elements stack next to each other and wrap to the next line if there's no horizontal space left.

    The same inline element: first without and then with line wrapping (from CSS: The Definitive Guide)

    Block boxes on the other hand are generated by elements such as div, p and table and are flowed vertically, even if there's space to flow them horizontally. By default a block box fills up the entire horizontal space it is given, unless the width of the element is set to a different value, as is demonstrated in the figure below.

     
    Two consecutive block elements (with different content, widths and margins) are flowed vertically (from Mike Hall's article on CSS Positioning).

    It is possible to control whether a box is displayed as inline or block by setting the display property to inline or block. This doesn't however if the element is inline or block, but just how it is displayed.

    Containing block
    Every element has a containing block, which defines where in the web page the element should be rendered. In case of normal flow, Meyer explains, "the containing block is formed by the content edge of the nearest block-level, table cell, or inline-block ancestor box." Often it will be the parent element that is the containing block its children. Note that an inline element can never be a containing block in normal flow.

    CSS2.1 also defines a series of rules for determining an element's containing block when the element is not in normal flow, for example when an element is floated or positioned. I will discuss these properties in a later post.   

    Conclusion
    These are the building blocks of basic CSS positioning. Every HTML element is converted to a graphical box. The CSS is used to adjust the default positioning. By setting the height, width, margin, padding and border it is possible to change how and where an element is rendered. In my next post I will explain what happens when a negative margin is used to position an element outside its parent.