Как браузер рендерит HTML, CSS и JavaScript
Как браузер рендерит HTML, CSS и JavaScript
Браузер получил HTML-страницу. Но HTML — это просто текст. Как он превращается в пиксели на экране? В этом уроке разберём процесс рендеринга: от получения HTML до построения DOM, CSSOM и Render Tree.
Два критически важных дерева
Браузер строит две структуры данных в памяти, без которых рендеринг невозможен:
- DOM (Document Object Model) — дерево, представляющее HTML-структуру страницы. Каждый HTML-элемент становится узлом дерева.
- CSSOM (CSS Object Model) — дерево, представляющее CSS-стили. Каждое CSS-правило становится узлом с вычисленными стилями.
Объединение DOM и CSSOM даёт Render Tree — дерево, которое уже можно отрисовать. Оно содержит только видимые элементы (без <head>, без display: none), но с вычисленными стилями для каждого.
Построение DOM: от байтов к дереву
Браузер получает HTML как поток байтов. Преобразование проходит стадии:
Байты → Символы → Токены → Узлы → DOM
- Байты в символы. Браузер смотрит Content-Type (обычно
text/html; charset=utf-8) и декодирует байты в Unicode-строку. - Символы в токены. HTML-парсер (tokenizer) проходит строку и выделяет токены: открывающие теги (
<div>), закрывающие (</div>), текст, комментарии. - Токены в узлы. Каждый токен создаёт узел DOM: элемент, текст, комментарий.
- Узлы в DOM-дерево. Узлы связываются в иерархию родитель–потомок согласно вложенности тегов.
HTML-парсер очень толерантен. Он прощает незакрытые теги, пропущенные кавычки, нестандартные элементы. Браузер никогда не падает с ошибкой парсинга — он применяет алгоритмы исправления и строит DOM как может:
<!-- Такой кривой HTML -->
<p>Текст <strong>жирный</p></strong>
<!-- Браузер построит: -->
<p>Текст <strong>жирный</strong></p>
Блокирующий парсинг: CSS и JavaScript
DOM строится инкрементально — браузер не ждёт полной загрузки HTML. Но есть два фактора, блокирующих построение:
- CSS блокирует рендеринг. Браузер не начнёт отрисовку, пока не загрузит и не распарсит все
<link rel="stylesheet">в<head>. Иначе был бы «flash of unstyled content» (FOUC): пользователь видит неоформленную страницу, а потом она резко преображается. - JavaScript (синхронный
<script>без async/defer) блокирует парсинг DOM. Когда парсер встречает<script>, он останавливается, ждёт загрузки скрипта, выполняет его, и только потом продолжает парсить HTML дальше. Это потому, что скрипт может писатьdocument.write()и менять парсинг.
<head>
<link rel="stylesheet" href="style.css"> <!-- блокирует рендеринг -->
<script src="app.js"></script> <!-- блокирует парсинг DOM -->
</head>
Решение: async и defer у скриптов, минимизация CSS в критическом пути.
Построение CSSOM
Параллельно с DOM браузер строит CSSOM. Он проходит все CSS-правила (из <link>, <style>, inline style="", user-agent stylesheet) и строит дерево стилей. В отличие от HTML-парсера, CSS-парсер строгий: невалидное правило молча пропускается.
CSSOM содержит вычисленные значения: font-size: 1.2em превращается в конкретные пиксели (например, 19.2px). Наследуемые свойства (цвет, шрифт) копируются от родителя.
Render Tree: объединение DOM и CSSOM
Имея DOM и CSSOM, браузер строит Render Tree:
DOM:
body
div.header
h1 "Заголовок"
div.content
p "Текст"
CSSOM:
h1 { font-size: 24px; color: black; }
.header { background: #eee; }
.content { width: 70%; }
Render Tree:
body (visible)
div.header (visible, background: #eee)
h1 (visible, font-size: 24px, color: black)
div.content (visible, width: 70%)
p (visible)
Из Render Tree исключены:
<head>и его содержимое (не отображается).- Элементы с
display: none(они есть в DOM, но не в Render Tree). - Псевдо-элементы
::before/::after— наоборот, есть в Render Tree, но не в DOM.
Каждый узел Render Tree знает свои геометрию, стили и порядок отрисовки.
Layout (reflow): вычисление геометрии
Имея Render Tree, браузер вычисляет точные координаты и размеры каждого элемента — это этап Layout (или reflow). Браузер проходит дерево сверху вниз, определяя:
- Ширину каждого элемента (зависит от родителя, CSS-свойств, содержимого).
- Высоту (зависит от содержимого, может требовать нескольких проходов).
- Позицию (x, y согласно потоковому позиционированию, float, flexbox, grid).
Layout — вычислительно дорогой этап. Изменение одного элемента может вызвать reflow всего документа (например, изменение ширины окна браузера).
Paint и Compositing
После Layout'а браузер рисует пиксели:
- Paint. Для каждого узла Render Tree генерируются команды рисования: «нарисовать фон цвета #eee», «нарисовать текст 'Заголовок' шрифтом 24px». Это происходит на отдельных слоях.
- Compositing. Слои собираются вместе в финальное изображение на экране. Свойства вроде
transformиopacityменяются только на этапе compositing — без повторного layout и paint. Поэтому анимации черезtransformработают быстро (60 fps), а черезleft/top— медленно (вызывают reflow).
Проверь себя
- Чем Render Tree отличается от DOM?
- Почему CSS в
<head>блокирует рендеринг? - Почему анимация
transform: translateX(100px)быстрее, чемleft: 100px?
- DOM содержит ВСЕ элементы HTML (включая
<head>,display: none). Render Tree содержит только видимые элементы с вычисленными стилями — готовые к отрисовке. - Без CSSOM нельзя построить Render Tree (неизвестны стили). Если бы браузер рендерил без CSS, пользователь видел бы «голый» HTML, а через мгновение — оформленный — это FOUC.
transformработает только на этапе compositing — GPU сдвигает готовый слой, не пересчитывая геометрию.leftвызывает Layout (reflow) — браузер заново вычисляет позиции всех элементов.
Что унести с урока
- Браузер строит два дерева: DOM (из HTML) и CSSOM (из CSS).
- Render Tree = DOM + CSSOM. Только видимые элементы с вычисленными стилями.
- CSS блокирует рендеринг, JS блокирует парсинг DOM.
- Этапы отрисовки: Layout (геометрия) → Paint (команды рисования) → Compositing (слои на экран).
В следующем уроке детальнее разберём Critical Rendering Path — как оптимизировать последовательность загрузки, чтобы страница показалась пользователю как можно быстрее.