Как браузер рендерит страницу?
Общее понимание
Как только страница загрузилась, браузер преобразует полученную от сервера строку с HTML в специальную абстракцию — DOM-дерево.
Когда парсер встречает ссылки, он создает запросы для их скачивания. Процесс парсинга HTML заканчивается когда все ресурсы скачаны, а сборка DOM-дерева завершена.
Далее браузер преобразует CSS в CSS-дерево.
Когда оба дерева созданы, браузер комбинирует их в одно — Render-дерево, в нем вычисляются стили для каждого видимого элемента страницы.
Далее браузер занимается компоновкой, определяя размеры и позицию элементов в Render-дереве.
Как только компоновка завершена — страница отрисовывается на экране.
Глубокое понимание
Общее понимание можно конкретизировать и разделить на CRP этапы (critical rendering path) или этапы критические рендеринга:
Построение DOM-дерева (Document Object Model)
Работать напрямую с HTML-разметкой довольно дорого и долго.
Поэтому сначала браузер парсит HTML, а затем создает его абстрактное представление — объектную модель документа (DOM).
Построение DOM является инкрементальным, то есть парсер последовательно преобразует весь HTML-документ в токены, из которых формирует узлы.
Чем больше количество узлов, тем дольше происходит формирование DOM.
Когда парсер находит теги с ссылками на внешние ресурсы, он запрашивает их скачивание. Такие запросы могут блокировать построение DOM, например:
Параллельно созданию DOM-дерева происходит создание CSSOM-дерева.
Построение CSSOM-дерева (CSS Object Model)
CSSOM-дерево содержит все данные о стилях DOM-дерева.
Преобразование CSS в CSSOM проходит так, чтобы вложенные узлы наследовали стили от родительских.
Процесс построения CSSOM не инкрементальный, то есть он блокирует процесс построения CSSOM-дерева до момента когда все CSS-правила будут получены и обработаны.
Это связано возможностью перезаписать CSS-правило из любого места CSS-документа.
Наименее специфичные селекторы срабатывают быстрее, например:
.foo {} /* сработает быстрее */
Для поиска .foo
понадобится одна операция.
.bar .foo {}
Сначала будут найдены все .foo
, а потом браузер пройдёт вверх по дереву в поисках родительского элемента .bar
Более специфичные селекторы требуют от браузера большего количества работы, но эти проблемы не стоят их оптимизации.
Создание Render-дерева (Render Tree)
Как только браузер составил DOM и CSSOM, он объединяет их в общее дерево рендеринга — Render-дерево.
Процесс объединения запускает проверку каждого DOM-узла, начиная от корневого, затем совмещает каждый элемент с его стилями из CSSOM.
Наличие свойства display: none
предполагает исключение узла и его потомков из процесса создания Render-дерева.
Когда Render-дерево построено запускается процесс компоновки.
Компоновка (планировка, layout, re-flow)
На этапе компоновки высчитывается геометрия макета: определяется расположение и размеры элементов, то как они будут влиять или не влиять друг на друга.
Компоновка делится на два этапа:
Глобальная компоновка
Просчитывает каждый элемент в дереве и является очень дорогой операцией, например:
Инкрементальная компоновка
Просчитывает частично только «грязные» элементы, которые появляются в случае:
О viewport
viewport
определяет ширину видимой области экрана, которая по умолчанию ровна 960px.
<meta name="viewport" content="width=device-width">
Установка width=device-width
делает ширину видимой области в 100% от ширины экрана устройства.
device-width
изменяется каждый раз, когда пользователь поворачивает телефон. Это приводит к запуску этапа компоновки, как и при изменении размеров окна в обычном браузере.
Производительность
На производительность компоновки влияет DOM — чем больше узлов, тем больше времени понадобится на перерасчёт позиций и размеров всех элементов.
Вызов компоновки во время скролла или анимации, может стать узким местом
Для уменьшения частоты и продолжительности этого этапа, группируйте обновления экрана и избегайте анимации свойств, связанных с box-моделью.
Отрисовка (paint, re-paint)
Когда Render-дерево создано, а компоновка произведена — браузер начинает рисовать страницу, располагая пиксели в нужные места экрана, чтобы все элементы страницы отобразились там где должны.
Отрисовка делится на два этапа:
Глобальная отрисовка
Первичная полная отрисовка всей страницы.
Инкрементальная отрисовка
Браузер делит весь viewport
на прямоугольные участки. Если изменения ограничены одним участком, то он обозначается «грязным», а это приводит к его перерисовкe.
Порядок отрисовки связан со стековым контекстом. В общих чертах, отрисовка начинается с заднего плана и постепенно переходит к переднему.
background-color background-image border children outline
Отрисовка является самым дорогим процессом.
Влияние на производительность отрисовки
- Создание запроса к статическому ресурсу из HTML
- Скорость получения ответа от сервера
- Загрузка статического ресурса
- Парсинг и выполнение скриптов
Для гладкой работы интерфейса процесс отрисовки должен укладываться в стандартную частоту кадров экрана — 60 fps или 16ms.
Дополнительно
Композитинг
Компоновка и отрисовка работают за счёт CPU, поэтому относительно медленные, это делает плавные анимации невероятно дорогими.
Для плавных анимаций в браузерах предусмотрен — композитинг.
Композитинг это разделение содержимого страницы на «слои», которые браузер будет перерисовывать. Эти слои друг от друга не зависят, поэтому изменение элемента в одном слое не затрагивает элементы из других слоёв, и перерисовывать их становится не нужно.
Применение таких свойств, как transform
, выносит элемент на отдельный композитный слой.
Перерисовка (reflow, repaint)
Процесс отрисовки — циклический. Браузер перерисовывает экран каждый раз, когда на странице происходят какие-то изменения, например, если в DOM-дереве добавился новый узел или изменился текст.
С помощью requestAnimationFrame
мы можем указать браузеру что хотим запускать анимацию в один цикл обновления — animation frame
.
Оптимизация CRP
- Уменьшайте количество критических ресурсов, откладывая их загрузку, помечая их как async и/или группируя их
- Оптимизируйте количество необходимых запросов, а так же размеры файлов
- Оптимизируйте порядок так, чтобы критические ресурсы загружались в первую очередь, сокращая таким образом длину критических этапов рендеринга.