Глубокое погружение в конвейер рендеринга браузера: Полный путь от DOM до пикселей и оптимизация производительности
Полный конвейер рендеринга
Когда браузер получает HTML, он проходит через следующие этапы для отрисовки содержимого на экране:
HTML → Парсинг → DOM-дерево
CSS → Парсинг → CSSOM-дерево
↘
DOM + CSSOM → Дерево рендеринга → Layout → Paint → Compositing → Пиксели
Каждый этап имеет чётко определённые входы и выходы — понимание этих границ является основой оптимизации производительности.
Этап 1: Парсинг
Парсинг HTML
Поток байтов → Символы → Токены → Узлы → DOM
Ключевые характеристики:
- Инкрементальный парсинг: HTML парсится как поток, без ожидания полной загрузки
- Блокировка скриптами:
<script>приостанавливает парсинг (если неasync/defer) - Предварительное сканирование: Браузер предварительно сканирует последующие теги
<link>и<script>для ранних загрузок
<!-- ❌ Блокирует парсинг -->
<script src="app.js"></script>
<!-- ✅ Не блокирует парсинг -->
<script src="app.js" defer></script>
<script src="analytics.js" async></script>
Парсинг CSS
Парсинг CSS не блокирует построение DOM, но блокирует рендеринг — браузер не будет рендерить страницу с неопределёнными стилями.
<!-- Критический CSS встроенный -->
<style>
.above-fold { /* Стили видимой области */ }
</style>
<!-- Асинхронная загрузка некритического CSS -->
<link rel="preload" href="rest.css" as="style"
onload="this.rel='stylesheet'">
Этап 2: Вычисление стилей
Сопоставление CSS-селекторов с DOM-элементами для вычисления итоговых вычисленных значений стилей каждого элемента.
Производительность сопоставления селекторов
/* ✅ Быстро: Сопоставление справа налево, ID находит напрямую */
#nav .item { }
/* ❌ Медленно: Универсальный селектор требует обхода всех элементов */
* .item { }
/* ❌ Медленно: Смежные селекторы могут вызвать возврат */
div > p + p { }
/* ✅ Быстро: BEM с одним именем класса */
.nav__item { }
Сложность вычисления стилей
| Операция | Сложность | Описание |
|---|---|---|
| Селектор по одному классу | O(1) | Прямой поиск по хеш-таблице |
| Селектор потомка | O(n) | Требует обхода вверх |
| Универсальный селектор | O(n) | Обходит все элементы |
:nth-child() |
O(n) | Требует вычисления позиции |
Этап 3: Layout
Вычисляет позицию и размер каждого элемента, генерируя дерево layout.
Операции, запускающие Reflow
| Операция | Область воздействия |
|---|---|
Изменение width/height |
Текущий элемент и потомки |
Изменение margin/padding |
Текущий элемент и последующие родственники |
Изменение font-size |
Текущий элемент и все потомки |
Изменение display |
Текущий элемент и все наследники |
Чтение offsetWidth и т.д. |
Принудительный синхронный layout |
window.getComputedStyle() |
Принудительный синхронный layout |
Ловушка принудительного синхронного layout
// ❌ Перемежённое чтение/запись — каждое чтение запускает reflow
elements.forEach(el => {
const height = el.offsetHeight; // Чтение → запускает reflow
el.style.height = height + 10 + 'px'; // Запись → помечает как грязный
});
// ✅ Пакетное разделение чтения/записи
const heights = elements.map(el => el.offsetHeight); // Пакетное чтение
elements.forEach((el, i) => {
el.style.height = heights[i] + 10 + 'px'; // Пакетная запись
});
Использование паттерна FastDOM
class FastDOM {
private reads: (() => void)[] = [];
private writes: (() => void)[] = [];
measure(fn: () => void) { this.reads.push(fn); }
mutate(fn: () => void) { this.writes.push(fn); }
flush() {
this.reads.forEach(fn => fn()); // Сначала пакетное чтение
this.writes.forEach(fn => fn()); // Затем пакетная запись
this.reads = [];
this.writes = [];
}
}
Этап 4: Paint
Растеризация элементов дерева layout в пиксели. Paint происходит слой за слоем.
Операции, запускающие Repaint
| Операция | Reflow? | Repaint? |
|---|---|---|
Изменение color |
❌ | ✅ |
Изменение background |
❌ | ✅ |
Изменение visibility |
❌ | ✅ |
Изменение box-shadow |
❌ | ✅ |
Изменение outline |
❌ | ✅ |
Изменение opacity |
❌ | ❌ (композитный слой) |
Изменение transform |
❌ | ❌ (композитный слой) |
Уменьшение области Paint
/* ❌ Изменение любого свойства может вызвать repaint всего слоя */
.card {
background: white;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
/* ✅ Повышение анимированных элементов до независимых композитных слоёв */
.animated-element {
will-change: transform;
/* или */
transform: translateZ(0);
}
Этап 5: Compositing
Комбинирование нескольких окрашенных слоёв в финальное изображение. Это задача GPU.
Условия повышения до композитного слоя
| Условие | Пример |
|---|---|
| 3D-трансформация | transform: translateZ(0) |
will-change |
will-change: transform, opacity |
<video> |
Видеоэлементы повышаются автоматически |
<canvas> |
Canvas 2D/WebGL |
| CSS-анимация/переход | Анимации opacity/transform |
position: fixed |
Элементы с фиксированным позиционированием |
filter |
Размытие, яркость и другие фильтры |
Принципы GPU-ускорения
Путь рендеринга CPU:
Изменение стиля JS → Reflow → Repaint → Compositing → Отображение
Время: 16-100ms
Путь рендеринга GPU (композитные слои):
Изменение JS transform/opacity → Compositing → Отображение
Время: 1-2ms (пропускает reflow и repaint)
Правильное использование will-change
/* ❌ Злоупотребление: Повышать каждый элемент, расходует память GPU */
* { will-change: transform; }
/* ✅ По необходимости: Повышать только перед анимацией */
.card {
transition: transform 0.3s;
}
.card:hover {
will-change: transform; /* Повышать только при наведении */
}
/* ✅ Динамическое управление через JS */
element.addEventListener('mouseenter', () => {
element.style.willChange = 'transform';
});
element.addEventListener('animationend', () => {
element.style.willChange = 'auto'; // Освободить после окончания анимации
});
Анализ рендеринга с DevTools
1. Панель Performance
Ключевые метрики:
- Зелёные полосы: Время Paint
- Фиолетовые полосы: Время Layout
- Оранжевые полосы: Время Compositing
- Красные треугольники: Длинные кадры (>16.67ms)
2. Панель Rendering
Включённые опции:
☑ Paint flashing → Зелёная подсветка отмечает repainted области
☑ Layout Shift Regions → Синие отметки показывают смещения layout
☑ Layer borders → Оранжевые границы отмечают композитные слои
☑ FPS meter → Мониторинг частоты кадров в реальном времени
3. Панель Layers
Просмотр списка композитных слоёв и использования памяти:
Слой #1 (корень) → 1200x800 → 3.8MB
Слой #2 (видео) → 640x360 → 0.9MB
Слой #3 (модальное) → 400x300 → 0.5MB
Итого: 5.2MB памяти GPU
Чеклист оптимизации производительности
Избегать Reflow
- ✅ Использовать
transformвместо анимацийtop/left - ✅ Группировать DOM-изменения (DocumentFragment / cloneNode)
- ✅ Разделять чтение и запись (паттерн FastDOM)
- ✅ Устанавливать
display:noneдля внеэкранных элементов перед изменением
Избегать Repaint
- ✅ Использовать только
transformиopacityдля анимаций - ✅ Использовать CSS-свойство
containдля ограничения области воздействия - ✅ Избегать крупноформатных
box-shadowиfilter
Использовать Compositing
- ✅
will-changeдля повышения композитных слоёв по необходимости - ✅ Повышать фиксированные элементы (header/footer) до независимых слоёв
- ✅ Мониторить память GPU для предотвращения взрыва слоёв
CSS-сдерживание
/* Ограничить область воздействия стилей/layout/paint */
.widget {
contain: layout paint style;
}
/* Строгое сдерживание: содержимое не влияет на внешнее */
.isolated-component {
contain: strict;
}
/* Сдерживание размера содержимого: подходит для элементов списка */
.list-item {
contain: content;
}
Значение contain |
Предотвращает распространение Reflow | Предотвращает распространение Repaint | Может содержать внеэкранное содержимое |
|---|---|---|---|
none |
❌ | ❌ | ❌ |
layout |
✅ | ❌ | ❌ |
paint |
✅ | ✅ | ✅ |
strict |
✅ | ✅ | ✅ |
content |
✅ | ✅ | ✅ |
Заключение
Понимание конвейера рендеринга браузера — основа оптимизации производительности фронтенда. Ключевой принцип: держать изменения на самой ранней возможной стадии — если проблему можно решить только на этапе compositing, никогда не запускайте reflow. Запомните три ключевых числа: reflow занимает 10-100ms, repaint занимает 1-10ms, compositing занимает 0.1-1ms. Используйте transform и opacity для анимаций, contain для ограничения области воздействия и will-change для повышения композитных слоёв по необходимости — это три столпа оптимизации производительности рендеринга.
Попробуйте эти локальные браузерные инструменты — регистрация не требуется →