📜 ⬆️ ⬇️

How z-index actually works

Probably almost every one of us at least once in his life used the z-index property. In addition, each developer is sure that he knows how it works. In fact, what could be simpler than operations with integers (comparing and assigning them to elements). But is everything as simple as it seems at first glance?

Perhaps the information I describe below is actually trivial. However, I am sure that many will find it useful for themselves. Those who already knew about it, will be able to use this text as a cheat sheet in difficult times. So, welcome under cat.

image

In fact, a person usually begins to try to figure out a new area for himself in three cases: if he encounters unexpected results at work and does not understand what is happening; if the need arises to go beyond and look at the object from a different angle; and finally, just for fun.
')
My case is clearly not in the third category. At first, several times in my life I came across the first scenario when working on different projects; however, he did not fully understand the question due to laziness and the lack of clear and understandable materials with examples. And then, at the beginning of this year, I began to write a web engine, which made me start reading standards and generally look at how different non-trivial things work in popular browsers, and most importantly, why they work that way.

Let's start with the simple. What is z-index and what is it for?

Obviously, this is the coordinate along the Z axis, given for some element. The Z axis is directed towards the user. A larger number is the closer element.

image

Why are z-index integers? It's simple. The range is practically unlimited above and below, so we do not need to use fractional values. Since a real monitor does not have a third dimension (we can only imitate it), we need some dimensionless quantity, the only task of which is to provide a comparison of the elements (that is, orderliness of the set). Integers do an excellent job with this task, while they are clearer than real numbers.

It would seem that this knowledge is enough to start using z-index on the pages. However, not all so simple.

<div style="background: #b3ecf9; z-index: 1"></div> <div style="background: #b3ecb3; margin-top: -86px; margin-left: 38px; z-index: 0"></div> 

image

Looks like something went wrong. We have made the first block z-index more than the second, so why is it displayed below? Yes, it follows the code earlier - but it would seem that this should play a role only with equal values ​​of z-index.

At this point, it's time to open the CSS2.1 standard, or rather, an appendix to it regarding the handling of overlay contexts. Here is the link .

From this small and very concise text you can immediately take out a lot of important information.

  1. z-index control overlaying not individual elements, but overlay contexts (groups of elements)
  2. We cannot arbitrarily manage elements in different contexts relative to each other: the hierarchy works here. If we are already in a “low” context, then we will not be able to make its element higher than the element of a “higher” context.
  3. z-index does not make sense at all for elements in a normal stream (for which the position property is static). We fell into this trap in the example above.
  4. In order for an element to set a new overlay context, it must be positioned, and it must be assigned a z-index.
  5. If the element is positioned, but the z-index is not specified, then we can conditionally assume that it is zero (for simple cases it works like this, we will consider the nuances later).
  6. And still separate contexts of imposing are set by elements with opacity value less than one. This was done so that you can easily transfer the alpha blending to the last rendering stage for processing by the video card.

But that's not all. It turns out that with elements without a z-index, everything is not as simple as it may seem.

The process of drawing elements of the context subtree can be divided into several stages (the first two of which are the direct output of the background color and the background image of the current element setting the context).

So, consider the entire list.

3. Derivation of child contexts with negative z-index
4. Output of the child block elements in the normal stream (only backgrounds)
5. Outputting child float elements
6. Displaying the content of elements in the normal flow: inline and inline-block descendants, inline content within block descendants, including lines of text *
7. Output of child contexts with zero and auto z-index **
8. Derivation of child contexts with positive z-index

* in order to bypass the depth-first tree
** for contexts with z-index: auto, all child contexts should be considered descendants of the current context, that is, pull them up to the current level

Not so easy, right? You can roughly illustrate this scheme with the following picture:

image

It is also possible to open an example on the codepen and play with it yourself .

But that's not all. It would seem that the algorithm is already quite complicated: we need to first pull up the child contexts inside pseudo-contexts (remember the value of auto?), Then sort the two z-index lists by building them into a number series, then go through the child elements: first by block in normal flow, then floating, then inline and inline block…

But here we are waiting for two surprises. The first, if you're lucky, will not touch us. It is connected with the fact that the background with frames and the contents of block elements are displayed at different stages - but if our samopisny engine for each text node creates an automatically inline element, then everything will be ok, they will naturally be displayed later.

But the second is not so trivial. It is in the mark
If you’re a little bit of a dog, you’ll have to do it.

have float and inline-block / inline (but not block!) elements.

What does this mean in practice? And this means that we must process them in the same way as the elements with z-index: auto. That is, first, bypassing their subtrees and pulling out their child contexts, placing them at the current level. But otherwise, we must treat them as elements that set their context. This means that the entire subtree inside them, which has stretched out after traversing to a linear list, must remain atomic. Or, in other words, we cannot shuffle the order of the elements so that the descendants of such an element “surface” above their parent. And if for child contexts it is intuitively clear (because the algorithm is recursive), then here it is no longer so much.

Therefore, when writing engine code, it’s necessary to go into trickery so that the float, inline and inline-block elements have not yet revealed their descendants (except for child elements with positioning and z-index, which form overlay contexts), and then run the entire function is recursive, but vice versa, taking into account the fact that child contexts should be skipped during the traversal.

A few examples to demonstrate this phenomenon:

 <div style="float: left; background: #b3ecf9;"> <div style="width: 40px; height: 40px; background: #fff700; position: relative; z-index: -1; top: -20px; left: -20px;"></div> </div> 

image

Here, the child element has a z-index and is positioned. It “floats up”, but is displayed under the blue square, since elements with negative z-index are displayed at stage 3, and float elements are displayed at stage 5.

 <div style="float: left; margin-top: -30px; background: #b3ecf9;"> <div style="width: 40px; height: 40px; background: #fff700; position: relative; z-index: 0;"></div> </div> <div style="background: #b3ecb3; margin-top: 52px; margin-left: 38px;"> <div style="width: 40px; height: 40px; background: #ff0000; position: relative; z-index: 0;"></div> </div> 

image

In this example, the second element (green) appears before the first (blue), and therefore below. However, children are pulled up (because they set their own contexts), so in this case they go in the same order in which they go exactly in the source tree (the order of their ancestors after the transposition is not important!). If the first child element is set to z-index equal to 1, then we will get the following image:

image

Add more items.

 <div style="float: left; background: #b3ecf9;"> <div style="float: left"> <div style="width: 40px; height: 40px; background: #fff700; position: relative; z-index: 0;"></div> </div> </div> <div style=" background: #b3ecb3; margin-top: 32px; margin-left: 40px;"> <div style="position: relative"> <div style="width: 40px; height: 40px; background: #ff0000; position: relative; z-index: 0;"></div> </div> </div> 

image

Here, the child contexts are pulled out of both floats and regular blocks, the order being preserved as it was in the source tree.

Finally, the last example:

 <div style="background: #b3ecf9;"> <div style="display: inline-block; width: 40px; height: 40px; background: #fc0;"></div> </div> <div style="background: #b3ecb3; margin-top: -100px; margin-left: 22px;"></div> 

image

As you can see, it is quite possible to “jump out” of a block element - unlike other cases, and since an inline-block element pops up, it will be displayed last in this document.

As you can see, the z-index allows you to perform many interesting tricks (which is at least hiding the element under its immediate parent with the help of a negative z-index in the descendant). I hope this article was useful to you.

Source: https://habr.com/ru/post/431046/


All Articles