Web page rendering: what the front-end developer should know about it

I welcome you, dear Habravites! Today I would like to highlight the issue of rendering in web development. Of course, a lot of articles have already been written on this topic, but, as it seemed to me, all the information is rather fragmented and fragmentary. At least, in order to collect the whole picture in my head and comprehend it, I had to analyze a lot of information (mostly English). That is why I decided to formalize my knowledge in an article, and share the result with the Habr community. I think the information will be useful to both novice web developers and more experienced ones to refresh and structure their knowledge.

This direction can and should be optimized at the stage of layout / frontend development, since, obviously, layout, styles and scripts are directly involved in the rendering. To do this, appropriate specialists must know some subtleties.

I note that the article is not intended to accurately convey the mechanics of browsers, but rather to understand its general principles. Moreover, different browser engines are very different in operating algorithms, so it is not possible to cover all the nuances within the framework of one article.

The process of processing a web page by a browser


To get started, consider the sequence of the browser when displaying a document:

  1. The DOM (Document Object Model) is formed from the HTML document received from the server.
  2. Styles are loaded and recognized, CSSOM (CSS Object Model) is formed.
  3. Based on the DOM and CSSOM, a rendering tree is formed, or a render tree is a set of rendering objects (Webkit uses the term “renderer”, or “render object”, and Gecko uses “frame”). The Render tree duplicates the DOM structure, but invisible elements (for example,, or elements with a style display:none;) do not fall here . Also, each line of text is represented in the rendering tree as a separate renderer. Each rendering object contains the corresponding DOM object (or block of text), and the style calculated for this object. Simply put, render tree describes the visual representation of the DOM.
  4. For each render tree element, the position on the page is calculated - layout occurs. Browsers use the flow method, in which in most cases a single pass is enough to accommodate all elements (more is needed for pass tables).
  5. Finally, all this stuff is rendered in the browser - painting.

In the process of user interaction with the page, as well as the execution of scripts, it changes, which requires the repeated execution of some of the above operations.

Repaint


If you change the style elements that do not affect its size and position on the page (eg, background-color, border-color, visibility), the browser simply renders it again, taking into account the new style - there is repaint (or restyle).

Reflow


If the changes affect the content, structure of the document, the position of the elements, a reflow (or relayout) occurs. The reasons for such changes are usually:

  • Manipulations with the DOM (adding, deleting, changing, rearranging elements);
  • Content changes, incl. text in form fields;
  • Calculation or change of CSS properties;
  • Adding, deleting style sheets;
  • Manipulations with the attribute " class";
  • Manipulations with the browser window - resizing, scrolling;
  • Activating pseudo-classes (for example, :hover).


Browser Optimization


Browsers, where possible, localize repaint and reflow within elements that have undergone change. For example, resizing an absolutely or fixed positioned element will affect only the element itself and its descendants, while changing a statically positioned element will entail a reflow of all elements following it.

Another feature - during the execution of JavaScript, browsers cache the changes made and apply them in one pass upon completion of the code block. For example, during the execution of this code, only one reflow and repaint will occur:

var $body = $('body');
$body.css('padding', '1px'); // reflow, repaint
$body.css('color', 'red'); // repaint
$body.css('margin', '2px'); // reflow, repaint
// На самом деле произойдет только 1 reflow и repaint

However, as described above, accessing element properties will cause a forced reflow. That is, if we add a call to the element property in the above code block, this will cause an extra reflow:

var $body = $('body');
$body.css('padding', '1px');
$body.css('padding'); // обращение к свойству, принудительный reflow
$body.css('color', 'red');
$body.css('margin', '2px');

As a result, we get 2 reflow instead of one. Therefore, if possible, access to the properties of elements should be grouped in one place in order to optimize performance (see a more detailed example on JSBin ).

But, in practice, there are situations when you can not do without a forced reflow. Suppose we have a task: we need to apply the same property to an element (take " margin-left") first without animation (set to 100px), and then animate it using transition to value 50px. You can immediately see this example on JSBin , but I will sign it here too .
First, let's create a class with transition:

.has-transition {
   -webkit-transition: margin-left 1s ease-out;
      -moz-transition: margin-left 1s ease-out;
        -o-transition: margin-left 1s ease-out;
           transition: margin-left 1s ease-out;
}

Then, let's try to implement our plan as follows:

var $targetElem = $('#targetElemId'); // наш элемент, по умолчанию у него присутствует класс "has-transition"
// убираем класс с transition
$targetElem.removeClass('has-transition');
// меняем свойство, ожидая, что transition отключён, ведь мы убрали класс
$targetElem.css('margin-left', 100);
// ставим класс с transition на место
$targetElem.addClass('has-transition');
// меняем свойство
$targetElem.css('margin-left', 50);

This solution will not work as expected since changes are cached and applied only at the end of the code block. A forced reflow will help us out, as a result, the code will take the following form, and will exactly fulfill the task:

// убираем класс с transition
$(this).removeClass('has-transition');
// меняем свойство
$(this).css('margin-left', 100);
// принудительно вызываем reflow, изменения в классе и свойстве будут применены сразу
$(this)[0].offsetHeight; // как пример, можно использовать любое обращение к свойствам
// ставим класс с transition на место
$(this).addClass('has-transition');
// меняем свойство
$(this).css('margin-left', 50);


Optimization Tips


Based on this article, as well as other articles on Harbe, which addresses the issue of optimizing the client side, you can derive the following tips that will come in handy when creating an effective frontend:

  • Write valid HTML and CSS, indicating the encoding. Styles are best included in , and scripts at the end .
  • Try to simplify and optimize CSS selectors (this is often neglected by developers using preprocessors). The less nesting the better. By processing efficiency, selectors can be arranged in the following order (starting with the fastest):
    1. Identifier: #id
    2. Grade: .class
    3. Tag: div
    4. Neighbor Selector: a + i
    5. Child selector: ul > li
    6. Universal selector: *
    7. Attribute selector: input[type="text"]
    8. All elements and pseudo-classes: a:hover

    It should be remembered that the browser processes selectors from right to left, so it is better to use the most effective ones - the identifier and the class - as the key (rightmost) selector.
    div * {...} // плохо
    .list li {...} // плохо
    .list-item {...} // хорошо
    #list .list-item {...} // хорошо
    

  • In scripts, minimize any work with the DOM. Cache everything: properties, objects, if they are meant to be reused. For complex manipulations, it is reasonable to work with an “offline” element (that is, which is not in the DOM, but in memory), followed by placing it in the DOM.
  • When using jQuery to select elements, follow the guidelines for compiling selectors .
  • To change the styles of elements, it is better to modify only the “ class” attribute , and as deep as possible in the DOM tree, this is both more competent from the point of view of development and support (separation of logic from presentation), and less costly for the browser.
  • It is desirable to animate only absolutely and fixedly positioned elements.
  • You can disable complex: hover animations while scrolling (for example, adding the " no-hover" class to the body ). An article on this topic .


For a more detailed study of the issue, I recommend that you read the articles:


I hope every reader has learned something useful from the article. In any case, thank you for your attention!

UPD: Thanks to SelenIT2 and piumosso for the correct comments regarding the efficiency of processing CSS selectors.

Also popular now: