Browser Rendering Flow
Rendering flow — процесс превращения HTML, CSS и JavaScript в пиксели на экране. Понимание этого процесса — ключ к оптимизации производительности.
Зачем нужно
Каждое изменение на странице проходит через rendering pipeline. Знание этапов позволяет понять: почему анимация тормозит, почему страница «дёргается», как избежать дорогих перерисовок. Разница между 60 FPS и 15 FPS — в понимании rendering flow.
Где используется
Оптимизация анимаций, скролла, динамического контента. Решение проблем с производительностью. Собеседования по фронтенду (частый вопрос).
Предпосылки
HTML, CSS, DOM API, Web Vitals
Полный Rendering Pipeline
HTML → Parse → DOM Tree
→ Render Tree → Layout → Paint → Composite → Pixels
CSS → Parse → CSSOM
Пошагово:
1. PARSING (Парсинг)
HTML → DOM Tree
CSS → CSSOM Tree
2. RENDER TREE (Дерево рендеринга)
DOM + CSSOM → Render Tree
(только видимые элементы, без display:none)
3. LAYOUT (Разметка)
Вычисление позиций и размеров каждого элемента.
«Где и какого размера каждый блок?»
4. PAINT (Отрисовка)
Заполнение пикселей: цвета, тени, текст, бордеры.
«Как выглядит каждый пиксель?»
5. COMPOSITE (Композиция)
Объединение слоёв в финальное изображение.
«Наложить слои в правильном порядке.»
Этап 1: Parsing (Парсинг)
DOM Tree
<html>
<body>
<h1>Привет</h1>
<p>Мир</p>
</body>
</html>
Document
└── html
└── body
├── h1
│ └── "Привет"
└── p
└── "Мир"
CSSOM Tree
body { font-size: 16px; }
h1 { color: blue; }
p { display: none; }
body (font-size: 16px)
├── h1 (color: blue, font-size: 16px)
└── p (display: none, font-size: 16px)
Блокировка парсинга
<!-- CSS блокирует рендеринг (не парсинг) -->
<link rel="stylesheet" href="styles.css">
<!-- JS блокирует парсинг! -->
<script src="app.js"></script>
<!-- HTML-парсер останавливается, пока JS не загрузится и выполнится -->
<!-- Решения: -->
<script src="app.js" defer></script> <!-- Выполнить после парсинга -->
<script src="app.js" async></script> <!-- Загрузить параллельно, выполнить сразу -->
| Атрибут | Загрузка | Выполнение | Порядок |
|---|---|---|---|
| (без) | Блокирует | Блокирует | По порядку |
async |
Параллельно | Сразу после загрузки | Не гарантирован |
defer |
Параллельно | После парсинга HTML | По порядку |
Этап 2: Render Tree
DOM + CSSOM → Render Tree
Render Tree содержит ТОЛЬКО видимые элементы:
✓ h1 (color: blue)
✗ p (display: none) — исключён!
✗ <head> — всегда исключён
✗ <script> — не визуальный
Этап 3: Layout (Reflow)
Браузер вычисляет геометрию: позицию (x, y) и размер (width, height) каждого элемента.
// Эти свойства ВЫЗЫВАЮТ layout (reflow):
element.offsetWidth; // Чтение → принудительный layout
element.offsetHeight;
element.getBoundingClientRect();
window.getComputedStyle(element);
// Эти CSS-свойства ВЫЗЫВАЮТ reflow при изменении:
// width, height, margin, padding, border
// top, left, right, bottom
// font-size, line-height
// display, position, float
Forced Synchronous Layout (Layout Thrashing)
// ПЛОХО: чтение-запись-чтение-запись → layout thrashing
const elements = document.querySelectorAll('.item');
elements.forEach(el => {
const width = el.offsetWidth; // Чтение → layout
el.style.width = (width * 2) + 'px'; // Запись → invalidate
// Следующее чтение вызовет НОВЫЙ layout!
});
// ХОРОШО: batch reads, then batch writes
const widths = ;
elements.forEach(el => {
widths.push(el.offsetWidth); // Все чтения сначала
});
elements.forEach((el, i) => {
el.style.width = (widths[i] * 2) + 'px'; // Все записи потом
});
Этап 4: Paint (Repaint)
Браузер заполняет пиксели: цвета, текст, тени, бордеры, изображения.
// Эти CSS-свойства вызывают ТОЛЬКО repaint (без layout):
// color, background-color, background-image
// box-shadow, border-color, outline
// visibility (НО НЕ display)
// Repaint дешевле layout, но всё ещё может тормозить
Этап 5: Composite
Браузер создаёт отдельные слои и объединяет их на GPU.
/* Эти свойства работают ТОЛЬКО на этапе composite — самые дешёвые: */
transform: translateX(100px); /* Не вызывает layout/paint */
opacity: 0.5; /* Не вызывает layout/paint */
/* Для создания нового слоя: */
.animated {
will-change: transform; /* Подсказка браузеру: создай слой */
/* или */
transform: translateZ(0); /* "Хак" для GPU-ускорения */
}
Reflow vs Repaint vs Composite
Reflow (Layout): Самый дорогой. Пересчитывает размеры и позиции.
Запускает: Layout → Paint → Composite
Repaint: Средний. Перерисовывает пиксели без изменения размеров.
Запускает: Paint → Composite
Composite: Самый дешёвый. Только перекладывает слои на GPU.
Запускает: Composite
| Изменяем | Что происходит | Стоимость |
|---|---|---|
width, height, margin |
Layout + Paint + Composite | Высокая |
color, background |
Paint + Composite | Средняя |
transform, opacity |
Composite only | Низкая |
Оптимизация анимаций
/* ПЛОХО: анимация через top/left → reflow каждый кадр */
.box {
position: absolute;
transition: top 0.3s, left 0.3s;
}
.box:hover {
top: 100px;
left: 100px;
}
/* ХОРОШО: анимация через transform → только composite */
.box {
transition: transform 0.3s;
}
.box:hover {
transform: translate(100px, 100px);
}
/* ПЛОХО: анимация width → reflow */
.progress {
transition: width 0.3s;
}
/* ХОРОШО: анимация scaleX → composite */
.progress {
transform-origin: left;
transition: transform 0.3s;
}
.progress.fill-50 {
transform: scaleX(0.5);
}
requestAnimationFrame
// ПЛОХО: обновление DOM в setTimeout
setTimeout(() => {
element.style.transform = `translateX(${x}px)`;
}, 16);
// ХОРОШО: синхронизация с кадрами браузера
function animate() {
element.style.transform = `translateX(${x}px)`;
x += 2;
if (x < 300) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
DevTools: Performance Panel
1. F12 → Performance → Record
2. Выполни действие (скролл, клик, анимация)
3. Stop → Анализ:
- Зелёные блоки: Paint
- Фиолетовые блоки: Layout
- Жёлтые блоки: JavaScript
- Красные треугольники: Long Tasks (> 50ms)
Частые ошибки
1. Layout thrashing
// Каждое чтение между записями вызывает принудительный layout
for (const el of elements) {
el.style.height = el.offsetHeight + 10 + 'px'; // read+write в цикле!
}
2. Анимация через top/left вместо transform
/* Каждый кадр = reflow. На мобильных — 15 FPS */
.animate { transition: left 0.3s; }
/* Каждый кадр = composite. 60 FPS */
.animate { transition: transform 0.3s; }
3. Злоупотребление will-change
/* ПЛОХО: создаёт слишком много слоёв, жрёт память */
* { will-change: transform; }
/* ХОРОШО: только для элементов, которые реально анимируются */
.animated-card { will-change: transform; }
Практика
- Открой Performance panel в DevTools и запиши скролл страницы — найди layout/paint события
- Создай анимацию через
top/left, потом перепиши наtransform— сравни FPS - Напиши пример layout thrashing и исправь его через batch reads/writes
- Используй
will-changeдля ускорения анимации карточки