All web programmers have to write CSS from time to time. When confronted with it for the first time, you will most likely find that understanding CSS is nonsense. And the truth is - they added borders here, they changed the color there ... JavaScript - this is a really complicated thing. CSS is a toy compared to it.

However, as you improve on web development, a frivolous attitude to CSS will be a thing of the past. Faced with something indescribably strange, you realize that you simply can’t imagine how CSS works, what is being done in its depths. Something similar was with me. The first couple of years after my studies, I was engaged in JavaScript-development of a full cycle, occasionally referring to CSS. I have always believed that my bread is JavaScript, I gave him all my time.
For example, participated in discussions on
JavaScript Jabber . In the last year, I decided to focus on the frontend and only then I realized that I could not, for example, debug style sheets in the same way as JS code.
')
About CSS, they often joke, but few took it seriously enough and tried to understand it. Who, when confronted with a problem, thoughtfully engaged in the search for errors, given the way CSS handles browsers? Instead, we grab the code from the first answer to the Stack Overflow, do not disdain all sorts of hacks, or simply ignore the problems.
As a result, too often, developers simply shrug their shoulders when the browser creates incomprehensible things with styles. It’s time to think that CSS is like black magic. However, any programmer knows that a computer is a machine for parsing and executing human-written commands, and CSS, in this regard, is no different from our favorite JS. And if so - CSS is quite possible to learn and use it meaningfully and productively.
Knowledge of the internal mechanisms of the CSS is useful in different situations. For example - with serious debugging and optimizing the performance of web pages. Often, when they talk and write about CSS, they focus on solving common problems. I want to talk about how it all works.
DOM and CSSOM
For a start, it is important to understand that in browsers, among other subsystems, there is a JavaScript-engine and a rendering engine. We, in this case, are interested in the latter. For example, we discuss the details that relate to WebKit (Safari), Blink (Chrome), Gecko (Firefox), and Trident / EdgeHTML (IE / Edge).
The browser, in the course of displaying a web page on the screen, performs a specific sequence of actions leading to the construction of an object model document (DOM, Document Object Model) and a style sheet object model (CSSOM, CSS Object Model). This sequence of actions, simply, can be represented as follows:
- Conversion : reading bytes of HTML and CSS code from disk or downloading them from the network.
- Tokenization : splitting the input data stream into fragments (for example: start tags, end tags, attribute names, attribute values), deleting unnecessary characters, such as spaces and line breaks.
- Lexing : this step is similar to tokenization, but here the type of each token is determined (for example: this token is a number, the one is a string literal, another one is the equality operator).
- Syntax analysis : here the system accepts a stream of tokens after lexing, interprets them using a specific grammar, and turns this stream into an abstract syntax tree.
After creating similar tree structures for CSS and HTML, the rendering engine generates, based on them, what is called a rendering tree. This is part of the document layout process.
The rendering tree is a model of the visual presentation of the document, which allows you to display the graphic elements of the document in the correct order. The rendering tree is constructed using the following algorithm:
- Starting at the root of the DOM tree, bypass all visible nodes.
- Skip the invisible nodes.
- For each visible node, find the corresponding rules in CSSOM and apply them.
- Generate visible nodes with content and styles computed for them.
- And finally, create a rendering tree, which includes, for all page elements visible on the screen, and their contents, and information about styles.
CSSOM can have a major impact on the rendering tree, but not on the DOM tree.
Rendering
After the rendering tree and page layout have been created, the browser can proceed to displaying elements on the screen. This is what this process looks like.
- Creating a page layout . This step involves calculating the size of the element and its position on the screen. Parent elements can affect children. Sometimes child elements can affect the parent.
- Rendering . During this stage, the rendering tree is converted into images that are the basis of what will be displayed on the screen. This includes text, colors, images, borders, and shadows. Rendering is usually done on several layers, while if the page's JavaScript code affects the DOM, the page can be redrawn several times.
- Layering At this stage, the layers are combined and the final image is formed, which will be visible on the screen. In this case, since the elements of the page can be displayed on different layers, the layers need to be brought together in the correct order.
Page rendering time depends on the characteristics of the rendering tree. In addition, the greater the width and height of the element - the longer the rendering time will be.
Adding various effects can increase the page rendering time. The output of graphic representations of elements is made in accordance with the context of the overlay, in the order in which they are located relative to each other. First, those elements that are located below are displayed, then those that are located above. Later, speaking about the
z-index
property, we will focus on this in more detail. For those who are well aware of visual information, here is a great
demonstration of the process of forming a graphic representation of the page.
Speaking about the output of a graphical representation of pages in browsers, hardware acceleration of graphics is often mentioned. In such cases, they usually mean the acceleration of mixing layers. We are talking about the use of video card resources to prepare for the output of web pages.
Mixing layers using hardware acceleration can significantly increase the rendering speed compared to the traditional approach, which uses only the processor. In connection with all this, it is impossible not to recall the
will-change
CSS property, the skillful use of which allows you to speed up the output of pages. For example, when using CSS transformations, the
will-change
property allows the browser to be prompted that the DOM element will be transformed in the near future. It looks like
will-change: transform
. This allows the GPU to transfer some operations for drawing and flattening layers, which can significantly improve the performance of pages containing many animated elements. You can improve performance with
will-change
by using the
will-change
constructs
will-change: scroll-position
,
will-change: contents
,
will-change: opacity
,
will-change: left, top
.
It is important to understand that some properties can cause a change in the page layout, while changing others only redraws it. Of course, from the point of view of performance, it is better if you can do only by redrawing the page with a constant layout.
For example, changing the color of an element will leave the layout unchanged, resulting in only a redrawing of the element. But a change in the position of an element will lead to a change in the layout, and to a redrawing of the element itself, its child elements, and, possibly, adjacent elements. Adding a DOM node will also result in layout recalculation and page redrawing. Serious changes, such as increasing the font size of an HTML element, change the layout and redraw the entire rendering tree.
If you are like me, then you are probably more familiar with DOM than with CSSOM, so let's pay some attention to CSSOM. It is important to note that by default, CSS is considered as a rendering blocking resource. This means that the browser will not render until CSSOM is fully available.
In addition, we must understand that there is no full compliance between DOM and CSSOM. The differences in these structures are due to the presence in the DOM of invisible elements, scripts, meta tags, tags with proprietary information that is not displayed on the screen, and so on. All this is not taken into account when building CSSOM, since it does not affect the graphical representation of the page.
Another difference between DOM and CSSOM is that context-independent grammar is used in CSS analysis. In other words, in the rendering engine there is no code that would bring CSS to some acceptable form, as is done when parsing HTML to create a DOM.
When analyzing characters that form HTML elements, the browser is forced to take into account the surrounding characters, it needs more than just the language specification, since some elements in the markup may be missing, however, the engine, by all means, needs to be displayed page. Hence the need for more complex data processing algorithms.
Finishing the talk about rendering, we briefly consider the path of the web page from the set of bytes stored on the server to the data structures on the basis of which its graphical representation is formed.
The browser performs an HTTP request by requesting a page from the server. The web server sends the response. The browser converts the data received from the server into tokens, which are then converted into DOM and CSSOM tree nodes. After these trees are ready, the rendering tree is built, on the basis of which the page layout is formed, layer-by-layer rendering of elements and layer blending are performed. The result is a web page that we see on the screen.
Selector Specificity
Now that we’ve gotten a little understanding of how the browser works, take a look at some of the most common moments that developers are confused about. To begin, let's talk about the specificity of selectors.
Very simply, the specificity of the selectors means applying cascading CSS rules in the correct order. However, there are many ways to select a tag using a CSS selector, and the browser needs a way to decide which style to assign to a particular tag. Browsers make similar decisions, calculating the specificity value for each selector.
Calculating indicators of the specificity of selectors confuses many JavaScript developers, so let's look at this in more detail. We will use the following example. There is a
div
tag with the class
container
. This tag has another
div
, the
id
which is
main
. Inside
main
there is a tag
p
, which contains the tag
a
.
<div class="container"> <div id="main"> <p> <a href="">Hello!</a> </p> </div> </div>
Now, without looking back, try to analyze the CSS below and say what color the link text will be in the
a
tag.
#main a { color: green; } pa { color: yellow; } .container #main a { color: pink; } div #main pa { color: orange; } a { color: red; }
Maybe red? Or green? Not. The link will be pink with a specificity value of 1.1.1. Here are the remaining results:
div #main pa: 1,0,3
#main a: 1,0,1
pa: 2
a: 1
To find these numbers you need to perform the following calculations:
- The first number is the number of ID selectors.
- The second number : the number of class selectors, attribute selectors (for example:
[type="text"]
, [rel="nofollow"]
) and pseudo-classes ( :hover, :visited
).
- Number of the number : the number of type selectors and pseudo-elements (
::before, ::after
)
For example, take a look at this selector:
#header .navbar li a:visited
The value of specificity for it will be 1,2,2. There is one ID, one class, one pseudo-class, and two element type selectors (
li
and
a
). This value can also be read as if there are no commas in it, instead of 1,2,2–122. Commas are only here to emphasize that we are confronted with not three-digit decimal number, but three numbers. This is especially important for theoretically possible results like 0.1,13. If you rewrite it in the form of 0113, it will be unclear how to return it to its original state.
Positioning elements
Now I would like to talk about positioning. Positioning elements and creating a page layout, as we have already seen, go hand in hand.
Layout design is a recursive process that can be invoked for the entire rendering tree as a result of a global change in styles, or only for a part of the tree, when the changes touch only certain elements that need to be redrawn. Here is one interesting observation that can be made by referring to the rendering tree and imagining that it uses absolute positioning. With this approach, the objects from which the page layout is built are not placed in the rendering tree in the same places as in the DOM tree.
Often people ask me about the advantages and disadvantages of using flexbox and float. Of course, flexbox is, in terms of usability, very well, being applied to the same element, the flexbox layout is rendered in about 3.5 ms, while rendering the float layout can take about 14 ms. Thus, in order to take into account small but important details, it makes sense for JS developers to maintain their knowledge in the field of CSS in the same good condition as the knowledge in the field of JavaScript.
Z-index property
And finally, I would like to talk about the
z-index
property. At first glance it seems that there’s nothing to talk about here. Each element in an HTML document may either be in front of or behind the others. In addition, it only works for positioned elements. If you set the
z-index
property for an element whose positioning is not explicitly specified, it will not change anything.
The key to finding and fixing
z-index
issues is to understand the concept of overlay contexts. Troubleshooting always begins with the root element of the overlay context. The overlay context is the concept of placing HTML elements in three-dimensional space, in particular along the Z axis, relative to the user in front of the monitor. In other words, it is a group of elements with a common parent, which together move along the Z axis, either closer to or further from the user.
Each overlay context has one HTML element as the root element. When the positioning and
z-index
properties are not used, the rules for interaction between elements are simple. The order of the overlay elements corresponds to the order of their appearance in HTML.
However, you can create new overlay contexts using properties that are different from the
z-index
, and here everything becomes more complicated. Among them is the
opacity
property, when this value is less than one,
filter
, when the value of this property is different from
none
, and
mix-blend-mode
, whose value is not
normal
. These properties actually create new overlay contexts. Just in case, I want to remind you that blend mode allows you to specify how the pixels on a certain layer interact with the visible pixels on the layers below this layer.
The
transform
property also triggers the creation of a new overlay context in cases where it is different from
none
. For example,
scale(1)
and
translate3d(0,0,0)
. Again, as a reminder, the
scale
property is used to resize an element, and
translate3d
allows the GPU to be used for CSS transitions, improving the quality of the animation.
Results
If for you this material has become the first step to a serious mastering of CSS, we hope you will very soon learn how to solve problems with styles yourself. And
here you can find a list of additional materials that will help you deepen and expand your knowledge.
Dear readers! If you know CSS well, please tell us about how you dealt with it. Share your experience. We are sure it will be useful to many.