January 12, 2020
CSS continues to be a topic that confuses even those who have been working with it for several years. It's easy for any developer to get by with a 'good enough' understanding of how it all works but if you want to avoid being plagued by the confusion or frustration that can easily arise when styling a page, whether it's something you have to do frequently or not, it's wise to take a moment and really understand the fundamentals of CSS. Let's start by digging into an area of CSS that seems simple yet baffles most the second they go to use it: positioning.
When you add any element to the DOM and don't provide it with a position declaration, the initial value of the position property for that element is static.
In the above screenshot you can see that I've simply added an image to the body of an HTML page and have so far applied no styling to it. This image is currently positioned statically. If I were to now add more elements to this page and continued to never add any positioning declarations, all the elements would be positioned statically.
The second you add a positioned declaration to an element, that element is now said to be 'positioned'. Positioning an element removes it from the document flow entirely which allows you to place it somewhere else on the screen.
Fixed positioning allows you to arbitrarily position an element within the viewport.
I've added a purple border to the viewport to make it easier to see and have added
position: fixedto our smoking praying mantis. I can now use top, bottom, left, and right to position the image as I please within the viewport. In the example above, the image is positioned 50px from the left and top of the viewport.
So now what happens when we start adding other elements to a page where an element has been given a fixed position? Let's check that out.
As you can see, I've gone ahead and added a couple of paragraphs, all which have the initial position of static. Our mantis, which has been removed from the document flow thanks to its fixed positioning, now has all other elements flowing behind it.
If we remove the fixed positioning, you can see the mantis image falls back into its position in the document flow as the second element and the left and top declarations now have no effect as they have no impact on the placement of statically positioned elements.
Potentially the most common use case for fixed positioning is for the modal. When a user opens a modal, you want everything else on the screen to sit behind it and for the modal to be to primary focus until the user closes the modal and removes it from the DOM.
Now we're entering the realm of positioning that often leaves people scratching their head and with good reason. When you apply
position: relative;to an element, it appears as though nothing has happened.
Looking at the screenshot above our mantis friend appears to be in the exact same position as what he was when he was statically positioned. When we added a fixed position the change was instant and immediately identifiable but this looks like nothing has happened. However, you must remember that since we have now applied a position to this element it has been removed from the document flow and we can now move our mantis around with top, left, bottom, and right, something you can't do on a statically positioned element.
Now our relatively positioned mantis is 50px from the 'top' and 50px from the left but perhaps when looking at this, our image isn't where you were expecting it to be. The left positioning looks correct but what's the deal with the top? When we used
top: 50pxon our mantis when it had a fixed position, the image sat 50px from the top of the viewport, possibly where you were expecting our image to be now. Yet, that's obviously not what's happening so what exactly is going on?
Unlike fixed positioning which bases its position on the viewport, when an element is relatively positioned, it bases it's positioning on the elements around it. As seen in the screen shot below, our mantis is not 50px from the top of the viewport but rather 50px from the element that comes before it in the document flow.
If we now position the image to be
bottom: 50pxinstead of 50px from the top, you can see that the image now sits 50px above where above the next element in the DOM.
Another important thing to note here is that a relatively positioned element has no impact on the position of the elements around it. It will sit be stacked on top of elements but its positioning does not move any other element as seen in the last two screenshots.
We've finally reached the position that most of us seem to have the most trouble wrapping our head around: absolute positioning.
To start with, I've gone ahead and added some divs to my page to act as containers to our image, and have nested our mantis like so:
Presently, none of the divs or the image are positioned, I've simply added some padding to each nested element and adjusted to height and width to allow us to easily visualise how they sit inside one another.
Let's start by adding
position: absolute; top: 50px; left: 50px;to our image and see what happens.
And so we have a comparison, now I'll add
position: relative;to the grandparent container.
Our mantis is definitely in a different position now but what actually happened?
Absolute positioning is very similar to fixed positioning but it has a different containing block. An element with a fixed position will always be contained by the viewport while an absolutely positioned element is based on the closest positioned ancestor. This is typically the parent, however, if the parent is not positioned, the browser will look up the DOM hierarchy until a positioned element is found.
In the first scenario, no ancestor was positioned so the browser made its way through all the ancestors and, upon finding no positioned element, based the image's position off on the 'initial containing block' which is an area with dimensions equal to the viewport size. In the second scenario, the browser did the same thing except this time it was able to stop at the element I've named 'grandparent' as this has been relatively positioned.
So what if we were to add
position: relative;to the parent container as well? Will the mantis be positioned based off of the parent or grandparent container?
As we can see in the screenshot above, adding
position: relative;to the parent resulted in the parent becoming the containing block for our mantis. The grandparent container still has a relative positioning, but the browser made the parent the container for our image as this is the image's nearest positioned element.
There's one last thing you need to remember to take into account when trying to position elements: stacking.
Most of us are probably familiar with z-indexing but not all of us completely get how it works and most likely, we also don't realise that it's not the only thing that impacts stacking.
In the above screenshot we have three positioned elements:
- grandparent: relative
- parent: relative
- image: absolute
Our great-grandparent and child elements are not positioned.
What would I do if I just want to shift our mantis back a little so I can get him to sit behind the text "I'm the parent."? Let's trying adding
z-index: -1and see what happens. I mean, if I want it to sit back 1 layer, this could work, right?
We can see that our mantis with a z-index of -1 is no longer visible but where exactly has it gone? Is it behind the child container (which I've now given a green background colour to make it easier for us to see that the child container is still there but our image is gone)? Is it behind one of our two positioned elements, or is it somewhere else?
I've added a transparent background to all the containers so we can see our smoking mantis, who is now sitting behind every element on the page.
Ok, so we found it, that's great, but now what the hell do I do to get my mantis to sit just behind "I'm the parent?" but in front of the other elements in between that??
Let's take a step back. Remember I said that z-indexing isn't the only thing that impacts stacking? As a browser parses HTML it creates what's called the render tree which is responsible for determining which order to paint elements. It's pretty straight forward when there are no positioned elements but this behaviour changes when elements are positioned, and again when elements have z-indexing applied.
When a browser parses HTML it paints all non-positioned elements first, followed by positioned elements. Relatively positioned elements are dependent upon the document flow, while absolutely positioned elements are positioned by its ancestor. Finally layering is controlled by z-indexing and applied last by the browser.
In the above screenshot I've added
z-index: -1;to the grandparent container and you'll notice that the only element we can now see is the great-grandparent container. When my browser rendered this page, it rendered the great-grandparent and child containers which currently have a static position, then the positioned elements (grandparent, parent, and image), and finally it applied layering. Our mantis still has a z-index of -1 but this line is now irrelevant. When the browser found
z-index: -1;on the grandparent it moved it, along with all of its children, behind its ancestors.
Let's now return to our initial problem of wanting to sit our image behind the text of the parent container. The solution is pretty simple. Our parent container is already positioned so all we need to do is add
We can see in the above screenshot that our mantis is now sitting behind the text of the parent container just as we had hoped. The browser rendered our un-positioned elements, followed by our positioned elements, and then layered our mantis with a z-index of -1 just behind our parent with a z-index of 1. When there was no element with a positive z-index, our mantis was positioned behind everything on our page. Now that we've told the browser how we want to layer the parent, it can place our mantis just behind that. The background is not in front of our mantis however, as this is the background of that container, allowing our image to essentially slot in between the text and the applied background colour.
One final thing to note here is that z-indexing does not work on statically positioned elements. If you want to apply layering to an element, you need to position it first.
Happy positioning. :)