Браузеру предстоит пройти много шагов, прежде чем HTML-ответ от сервера будет преобразован в пиксели на экране. Последовательность этих шагов, необходимая для первого отображения страницы, называется «Критический путь рендеринга» (ориг. Critical Rendering Path).
Знание о CRP (Critical Rendering Path) невероятно полезно для понимания того, как улучшить производительность сайта.
Существует 6 этапов CRP:
- построение DOM-дерева,
- построение CSSOM-дерева,
- запуск JavaScript,
- создание Render-дерева,
- генерация раскладки,
- отрисовка.
Построение DOM-дерева
DOM (объектная модель документа) дерево это объект, представляющий полностью разобранную HTML-страницу. Начиная с корневого элемента <html>
, узлы создаются для каждого элемента/текста на странице. Элементы, вложенные в другие элементы, представлены в виде дочерних узлов, и каждый узел содержит полный набор атрибутов для этого элемента. Например, элемент <a>
будет иметь атрибут href
, связанный с узлом.
Возьмём для примера такой документ:
<html>
<head>
<title>Understanding the Critical Rendering Path</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<h1>Understanding the Critical Rendering Path</h1>
</header>
<main>
<h2>Introduction</h2>
<p>Lorem ipsum dolor sit amet</p>
</main>
<footer>
<small>Copyright 2017</small>
</footer>
</body>
</html>
Из него будет построено такое DOM-дерево:
![DOM-дерево](https://habrastorage.org/files/1d3/c64/dbd/1d3c64dbdadf4fcab2192a85f74a8453.png)
Хорошая новость, касательно HTML, заключается в том, что он может быть исполнен по частям. Документ не должен быть загружен полностью для того, чтобы контент начал появляться на странице. Однако, другие ресурсы, такие как CSS и JavaScript, могут блокировать отрисовку страницы.
Построение CSSOM-дерева
CSSOM (объектная модель CSS) — это объект, представляющий стили, связанные с DOM. Он выглядит так же как DOM, но с соответствующими стилями для каждого узла. Не имеет значения были ли стили объявлены явно или наследуются.
В файле style.css
, подключающемся в ранее упомянутом документе, мы имеем следующий набор стилей:
body { font-size: 18px; }
header { color: plum; }
h1 { font-size: 28px; }
main { color: firebrick; }
h2 { font-size: 20px; }
footer { display: none; }
С его помощью получится следующее CSSOM-дерево:
![CSSOM-дерево](https://habrastorage.org/files/9b5/33f/f95/9b533ff950dc4d528fc19d0f615654e5.png)
CSS считается «блокирующим обработку ресурсом». Это значит, что Render-дерево (см. ниже) не может быть построено без полного первоначального разбора CSS.
В отличии от HTML, CSS не может быть использован по частям в силу своей каскадной природы. Стили, описанные в документе ниже, могут переопределять и изменять стили, определённые ранее. Так что если мы начнём использовать CSS-стили до того, как будет разобрана таблица стилей, мы можем столкнуться с ситуацией, когда стили будут применяться неверно. Это означает, что для перехода к следующему шагу, необходимо полностью разобрать CSS.
CSS-файлы блокируют рендер только если они применяются. <link rel="stylesheet">
может принимать медиа-атрибут, в котором мы можем указать любое медиавыражение, к которому будут относиться вложенные внутрь стили. Если, например, мы имеем таблицу стилей с медиа-атрибутом orientation:landscape
, а мы просматриваем страницу в портретном режиме, то этот ресурс не будет считаться блокирующим обработку.
CSS также может являться «блокирующим скрипты», потому что JavaScript-файлы должны дождаться построения CSSOM, прежде чем начать исполняться.
Запуск JavaScript
JavaScript является блокирующим ресурсом для парсера. Это означает, что JavaScript блокирует разбор самого HTML-документа.
Когда парсер доходит до тега <script>
(не важно внутренний он или внешний), он останавливается, забирает файл (если он внешний) и запускает его. Вот почему, если мы имеем JavaScript-файл, который ссылается на элементы документа, мы обязательно должны поместить его после их появления.
JavaScript можно загружать асинхронно, указав атрибут async
, для того, чтобы избежать блокировки парсера.
<script async src="script.js">
Создание Render-дерева
Render-дерево — это совокупность DOM и CSSOM. Это дерево, которое даёт представление о том, что в конечном итоге будет отображено на странице. Это означает, что оно захватывает только видимый контент и не включает, например, элементы, которые были скрыты с помощью CSS-правила display: none
.
На примерах DOM и CSSOM, представленных выше, будет построено такое Render-дерево:
![Render-дерево](https://habrastorage.org/files/26c/384/b4b/26c384b4b75f410d936037ad160ca3ac.png)
Генерация раскладки
Раскладка — это то, что определяет размер видимой области документа (viewport), которая обеспечивает контекст для стилей CSS, зависимых от него, например, проценты или единицы вьюпорта.
Размер вьюпорта определяется метатэгом, находящемся в <head>
документа или, если тэг не представлен, будет использовано стандартное значение вьюпорта шириною в 980 пикселей.
Например, наиболее частым значением для этого метатэга является размер, соответствующий с шириной устройства.
<meta name="viewport" content="width=device-width,initial-scale=1">
Если пользователь посещает веб-страницу с устройства, ширина которого, например 1000 пикселей, то размеры будут опираться на это значение. Половина видимой области будет равна 500 пикселей, 10 процентов — 100 пикселей, и так далее.
Отрисовка
Наконец, на шаге отрисовки, видимый контент страницы может быть преобразован в пиксели, чтобы появиться на экране.
Время, которое займет этот этап, зависит как от величины DOM, так и от того, какие стили применяются. Некоторые стили требуют больше усилий, чтобы быть применёнными, чем другие. Например, сложное градиентное фоновое изображение потребует больше времени, чем простой сплошной цвет на фоне.
Собираем всё вместе
Чтобы увидеть как происходит критический путь рендеринга, мы можем воспользоваться инструментами разработчика. В Chrome это можно сделать во вкладке «Timeline» (в Canary, а так же скоро в стабильных версия Chrome, вкладка переименована в «Performance»).
Возьмем для примера наш HTML-образец, упомянутый выше (с добавленным тэгом <script>
).
<html>
<head>
<title>Understanding the Critical Rendering Path</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<h1>Understanding the Critical Rendering Path</h1>
</header>
<main>
<h2>Introduction</h2>
<p>Lorem ipsum dolor sit amet</p>
</main>
<footer>
<small>Copyright 2017</small>
</footer>
<script src="main.js"></script>
</body>
</html>
Если мы посмотрим в Event Log для этой страницы, то увидим следующее:
![Результат Event Log](https://habrastorage.org/files/aef/ae1/1ec/aefae11ecc6e427e8d009277bb36b70d.png)
- Send Request — GET-запрос, отправленный для index.html;
- Parse HTML and Send Request — начать разбор HTML и построение DOM. Отправить GET запрос для style.css и main.js;
- Parse Stylesheet — CSSOM, созданный для style.css;
- Evaluate Script — вычислить (выполнить) main.js;
- Layout — генерация раскладки, основанной на значении метатега viewport;
- Paint — отрисовка пикселей в документе.
Опираясь на эту информацию, мы можем принимать решения, направленные на оптимизацию критического пути рендеринга. Я расскажу о некоторых таких техниках в дальнейших публикациях.