Для чего Frontend разработчику знать об этапах отрисовки?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Зачем Frontend разработчику знать об этапах отрисовки (Rendering Pipeline)
Этапы отрисовки браузера — это критическое знание для оптимизации производительности и написания быстрого кода. Вот почему это важно.
Основные этапы отрисовки (Rendering Pipeline)
┌─────────────────────────────────────────────────┐
│ 1. Parsing │
│ HTML → DOM Tree │
│ CSS → CSSOM Tree │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ 2. Style Calculation │
│ Matching selectors → Style rules │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ 3. Layout (Reflow) │
│ Calculate positions & sizes │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ 4. Paint │
│ Draw pixels, text, images │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ 5. Composite │
│ Combine layers, send to GPU │
└─────────────────────────────────────────────────┘
↓
Display
Почему это важно для Frontend разработчика
1. Понимание производительности
Когда вы меняете свойства CSS, браузер может сделать разное:
// Каждое изменение запускает весь pipeline!
element.style.width = '100px'; // Layout + Paint + Composite
element.style.backgroundColor = 'red'; // Paint + Composite (быстрее!)
element.style.transform = 'translateX(10px)'; // Только Composite (быстрее всех!)
// Результат: transform быстрее в 10x раз, чем width!
2. Избегание Forced Reflow
Если вы читаете размеры после изменения, браузер принудительно пересчитывает Layout:
// ПЛОХО: вызывает forced reflow!
for (let i = 0; i < 100; i++) {
element.style.width = i + 'px';
console.log(element.offsetWidth); // Браузер вынужден пересчитать Layout!
}
// Результат: 100 Layout операций = медленно
// ХОРОШО: читаем один раз
const width = element.offsetWidth;
for (let i = 0; i < 100; i++) {
element.style.width = (width + i) + 'px';
}
// Результат: батчинг изменений = быстро
3. Оптимизация анимаций
// ПЛОХО: меняем width → Layout + Paint каждый frame
// 60fps = 60 Layout + Paint операций в секунду
function animate() {
element.style.width = (element.offsetWidth + 1) + 'px';
requestAnimationFrame(animate);
}
// ХОРОШО: используем transform → только Composite
// 60fps = 60 Composite операций (намного быстрее)
function animate() {
let offset = 0;
function frame() {
offset++;
element.style.transform = `translateX(${offset}px)`;
requestAnimationFrame(frame);
}
frame();
}
Фаза 1: Parsing
Что происходит
HTML → DOM Tree (Document Object Model)
CSS → CSSOM Tree (CSS Object Model)
Как это влияет на код
<!-- ПЛОХО: Blocking CSS -->
<head>
<link rel="stylesheet" href="large-style.css"> <!-- Блокирует парсинг HTML -->
</head>
<body>
<h1>Content</h1>
</body>
<!-- ХОРОШО: Async CSS loading -->
<head>
<link rel="stylesheet" href="critical-style.css"> <!-- Critical CSS inline -->
<link rel="preload" href="non-critical.css" as="style">
</head>
<!-- ПЛОХО: Script в середине парсинга -->
<body>
<h1>Content</h1>
<script src="heavy-script.js"></script> <!-- Блокирует парсинг -->
</body>
<!-- ХОРОШО: Скрипты в конце или async -->
<body>
<h1>Content</h1>
<script src="heavy-script.js" defer></script>
</body>
Фаза 2: Style Calculation
Что происходит
Браузер матчит селекторы CSS и вычисляет стили
Как это влияет на код
/* ПЛОХО: сложные селекторы медленнее */
body > div > section > article > .container > p { color: red; }
/* ХОРОШО: простые селекторы быстрее */
.article-text { color: red; }
/* ПЛОХО: много селекторов */
.header, .nav, .sidebar, .main, .footer { margin: 0; }
/* ХОРОШО: одно правило */
body { margin: 0; }
Фаза 3: Layout (Reflow)
Что происходит
Браузер вычисляет позицию и размер каждого элемента
Как это влияет на код
// Свойства которые вызывают Layout (Reflow):
offsettop, offsetLeft, offsetWidth, offsetHeight
clientTop, clientLeft, clientWidth, clientHeight
scrollTop, scrollLeft, scrollWidth, scrollHeight
getComputedStyle()
getBoundingClientRect()
Element.offsetParent
Element.scrollIntoView()
// ПЛОХО: вызываем Layout каждый раз
for (let i = 0; i < elements.length; i++) {
const width = elements[i].offsetWidth; // Layout!
elements[i].style.width = (width * 0.9) + 'px';
}
// ХОРОШО: читаем один раз
const widths = elements.map(el => el.offsetWidth); // Layout один раз!
elements.forEach((el, i) => {
el.style.width = (widths[i] * 0.9) + 'px';
});
Фаза 4: Paint
Что происходит
Браузер рисует пиксели (текст, изображения, фоны)
Как это влияет на код
/* ПЛОХО: дорогие paint операции */
box-shadow: 0 0 10px rgba(0,0,0,0.5); /* Рисует тень вокруг элемента */
text-shadow: 0 0 5px black;
filter: blur(10px); /* Очень дорого! */
/* ХОРОШО: дешёвые операции */
transform: scale(1.1); /* Только Composite! */
opacity: 0.5; /* Только Composite! */
/* ПЛОХО: много элементов с градиентами */
.gradient { background: linear-gradient(45deg, red, blue); }
/* ХОРОШО: используй один раз для контейнера */
.gradient-container { background: linear-gradient(45deg, red, blue); }
Фаза 5: Composite
Что происходит
Браузер объединяет слои (layers) и отправляет на GPU
Как это влияет на код
// Свойства которые не вызывают Layout или Paint:
// transform, opacity, filter (в некоторых случаях)
// МЕДЛЕННАЯ анимация (Layout + Paint)
function slowAnimate() {
let position = 0;
setInterval(() => {
position += 10;
element.style.left = position + 'px'; // Reflow + Paint
}, 16);
}
// Результат: ~60fps, но может быть jank
// БЫСТРАЯ анимация (только Composite)
function fastAnimate() {
let position = 0;
function frame() {
position += 10;
element.style.transform = `translateX(${position}px)`; // Только Composite!
requestAnimationFrame(frame);
}
frame();
}
// Результат: гладкие 60fps, никаких проблем
Практический пример: Оптимизация списка
// ПЛОХО: вызывает много Layout операций
function renderItems(items) {
const container = document.getElementById('container');
items.forEach(item => {
const el = document.createElement('div');
el.textContent = item.text;
el.style.height = item.height + 'px'; // Layout!
el.style.marginBottom = '10px';
container.appendChild(el); // Trigger Layout!
});
}
// Результат: N Layout операций для N элементов
// ХОРОШО: батчим обновления
function renderItems(items) {
const container = document.getElementById('container');
const fragment = document.createDocumentFragment();
items.forEach(item => {
const el = document.createElement('div');
el.textContent = item.text;
el.style.height = item.height + 'px';
el.style.marginBottom = '10px';
fragment.appendChild(el); // Не добавляем в DOM
});
container.appendChild(fragment); // Одна Layout операция!
}
// Результат: 1 Layout операция для всех элементов
Core Web Vitals и Rendering Pipeline
LCP (Largest Contentful Paint) — зависит от:
├─ Parsing (быстро ли загружаются ресурсы)
├─ Style Calculation
├─ Layout
└─ Paint
FID (First Input Delay) — зависит от:
├─ JavaScript execution time
└─ Длины задач (отложит отрисовку)
CLS (Cumulative Layout Shift) — зависит от:
└─ Неожиданные Layout изменения
DevTools для отладки
Chrome DevTools - Performance
// 1. Открыть DevTools → Performance
// 2. Нажать Record
// 3. Выполнить операцию
// 4. Остановить Record
// 5. Анализировать:
// Синие блоки = Parsing
// Зелёные блоки = Style Calculation + Layout
// Оранжевые блоки = Paint
// Фиолетовые блоки = Composite
Chrome DevTools - Rendering
// DevTools → More tools → Rendering
// Включить:
// - Paint flashing (показывает какие области перерисовываются)
// - Layout shift regions (показывает CLS)
// - Frame rendering stats (показывает FPS)
Best Practices
1. Используй transform для анимаций
/* Вместо: */
.slow { animation: slide 1s; }
@keyframes slide { from { left: 0; } to { left: 100px; } }
/* Используй: */
.fast { animation: slide 1s; }
@keyframes slide { from { transform: translateX(0); } to { transform: translateX(100px); } }
2. Батчи DOM операции
// DocumentFragment для批量添加
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
fragment.appendChild(div);
}
document.body.appendChild(fragment); // Одна операция
3. Избегай forced reflow
// ПЛОХО
for (let el of elements) {
el.style.width = el.offsetWidth + 'px'; // Forced reflow каждый раз
}
// ХОРОШО
const widths = elements.map(el => el.offsetWidth); // Batch read
elements.forEach((el, i) => {
el.style.width = widths[i] + 'px'; // Batch write
});
4. Используй will-change аккуратно
/* Только для элементов которые часто меняются */
.animated {
will-change: transform;
animation: rotate 2s infinite;
}
Итог
Знание этапов отрисовки важно для:
- Оптимизации производительности — знаешь какие операции дорогие
- Написания быстрого кода — выбираешь правильные CSS свойства
- Отладки проблем — можешь использовать DevTools эффективно
- Достижения Core Web Vitals — улучшаешь LCP, FID, CLS
- Создания smooth анимаций — используешь transform вместо позиций
- Масштабирования приложений — оптимизируешь списки и таблицы
Простое правило:
- Layout (переток) — вызывают offsetWidth, offsetHeight, getComputedStyle()
- Paint — вызывают box-shadow, text-shadow, filter, background-image
- Composite — transform, opacity (дешёво!)
Зная эти этапы, ты сможешь писать код который работает в 10 раз быстрее!