Как происходит отрисовка страницы?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Процесс рендеринга браузером
Отрисовка страницы — это сложный многоэтапный процесс, при котором браузер преобразует HTML, CSS и JavaScript в пиксели на экране. Понимание этого процесса критично для оптимизации производительности. Процесс состоит из нескольких фаз, каждая из которых может стать узким местом.
Основные этапы рендеринга (Critical Rendering Path)
1. PARSING (Синтаксический анализ)
↓
2. STYLE (Вычисление стилей)
↓
3. LAYOUT (Расчёт размеров и позиций)
↓
4. PAINT (Отрисовка пикселей)
↓
5. COMPOSITE (Композиция слоёв)
Детально: каждый этап
1. Parsing (0-5 мс)
Браузер получает HTML и начинает парсить его:
- Создаёт DOM (Document Object Model) дерево
- При встрече с CSS — стройт CSSOM (CSS Object Model)
- При встрече с JS — парсит и выполняет (может заблокировать парсинг)
<!-- Блокирующий скрипт останавливает парсинг -->
<head>
<script src="blocking.js"></script> <!-- Парсинг остановится -->
</head>
<body>
<!-- Контент -->
<!-- Асинхронный скрипт не блокирует парсинг -->
<script src="async.js" async></script>
<!-- Отложенный скрипт выполнится после парсинга -->
<script src="deferred.js" defer></script>
</body>
2. Style Calculation (5-10 мс)
Браузер вычисляет финальные стили для каждого элемента:
- Cascade: специфичность, наследование
- Комбинирует встроенные стили, CSS правила, browser defaults
- Результат: каждый элемент имеет вычисленные стили
/* CSSOM создаёт правила */
.container {
width: 100%;
padding: 16px;
}
.item {
color: blue;
font-size: 16px;
}
/* Браузер вычисляет итоговые стили для каждого элемента */
3. Layout (10-20 мс)
Браузер рассчитывает точное положение и размер каждого элемента:
- Ширина, высота, margin, padding, position
- Layout triggering: изменение margin, width может затронуть весь документ
- Это дорогая операция
// ОПАСНО: Запускает Layout Thrashing
// (множество перерасчётов подряд)
const element = document.getElementById('item');
for (let i = 0; i < 1000; i++) {
const width = element.offsetWidth; // Читаем (запускает layout)
element.style.width = (width + 1) + 'px'; // Пишем (запускает layout)
// Повторяется 1000 раз = 2000 layouts!
}
// ПРАВИЛЬНО: Батчим чтения и записи
const element = document.getElementById('item');
let width = element.offsetWidth; // Одно чтение
for (let i = 0; i < 1000; i++) {
width += 1;
}
element.style.width = width + 'px'; // Одна запись
4. Paint (20-50 мс)
Браузер отрисовывает пиксели:
- Для каждого видимого элемента рисует его содержимое
- Разбивает на слои (layers) для оптимизации
- Дорогая операция, может заблокировать main thread
/* ИЗБЕГАЙ: Частых перекрашиваний */
div {
transition: all 0.3s ease; /* Все свойства = много paint */
}
/* ИСПОЛЬЗУЙ: Только fast-to-paint свойства */
div {
transition: transform 0.3s ease; /* transform не требует layout, только composite */
transition: opacity 0.3s ease; /* opacity тоже быстро */
}
/* ИЗБЕГАЙ: Сложные селекторы и ::before/::after */
.item:nth-child(n) .nested > div span::before { /* Медленно */
}
/* ИСПОЛЬЗУЙ: Простые селекторы */
.item-label { /* Быстро */
}
5. Composite (5-10 мс)
Браузер объединяет слои в финальное изображение:
- Самая быстрая операция из всех
- Нужна для z-index, opacity, transform
Оптимизация: как улучшить время отрисовки
// ===== ОПТИМИЗАЦИЯ #1: Асинхронный JavaScript =====
// Синхронный скрипт блокирует парсинг
<script src="script.js"></script> // Парсинг остановится
// Асинхронный скрипт
<script src="script.js" async></script> // Парсинг продолжится
// Отложенный скрипт (рекомендуется)
<script src="script.js" defer></script> // Выполнится после парсинга
// ===== ОПТИМИЗАЦИЯ #2: CSS в head, JS в end body =====
<head>
<link rel="stylesheet" href="styles.css"> <!-- CSS блокирует render -->
</head>
<body>
<!-- Контент -->
<script src="script.js"></script> <!-- JS в конце -->
</body>
// ===== ОПТИМИЗАЦИЯ #3: Минимизировать переполняющие Layout =====
// ПЛОХО: Перелаёты при каждой строке цикла
for (let i = 0; i < items.length; i++) {
items[i].style.width = (items[i].offsetWidth + 10) + 'px';
// offsetWidth запускает Layout!
}
// ХОРОШО: Вычислить один раз
const widths = items.map(item => item.offsetWidth);
widths.forEach((width, i) => {
items[i].style.width = (width + 10) + 'px';
});
// ===== ОПТИМИЗАЦИЯ #4: Использовать requestAnimationFrame =====
// Синхронизирует код с браузером (60 FPS)
requestAnimationFrame(() => {
element.style.transform = 'translateX(100px)';
});
// Вместо
setTimeout(() => {
element.style.transform = 'translateX(100px)';
}, 16);
// ===== ОПТИМИЗАЦИЯ #5: Использовать CSS для анимаций =====
// CSS анимации работают на GPU (быстро)
@keyframes slide {
from { transform: translateX(0); }
to { transform: translateX(100px); }
}
.element {
animation: slide 1s ease;
}
// Вместо JavaScript (медленно)
let pos = 0;
const interval = setInterval(() => {
pos += 1;
element.style.left = pos + 'px'; // Запускает layout каждый кадр
}, 16);
Инструменты для анализа производительности
// Chrome DevTools: Performance API
performance.mark('start');
// Какой-то код
performance.mark('end');
performance.measure('operation', 'start', 'end');
const measures = performance.getEntriesByName('operation');
console.log(measures[0].duration); // Время выполнения в мс
Практическое правило
Каждый рендер цикл (frame) должен занимать менее 16 мс (для 60 FPS). Если дольше — браузер не успевает, видны фризы.