Перейти к содержимому

3. Critical Rendering Path

Critical Rendering Path (CRP) — это последовательность шагов, которые браузер выполняет для превращения HTML/CSS/JS в пиксели на экране.

HTML → DOM → CSSOM → Render Tree → Layout → Paint → Composite
  1. HTML Parsing → DOM (Document Object Model)
  2. CSS Parsing → CSSOM (CSS Object Model)
  3. DOM + CSSOM → Render Tree (только видимые элементы)
  4. Layout → вычисление позиций и размеров
  5. Paint → рисование пикселей
  6. Composite → наложение слоёв

Браузер НЕ будет рисовать страницу, пока не загрузит весь CSS.

<!-- ❌ Блокирует рендеринг -->
<link rel="stylesheet" href="styles.css">
<!-- ✅ Critical CSS inline -->
<style>
/* Только стили для первого экрана */
body { font-family: sans-serif; margin: 0; }
.hero { background: #000; color: #fff; padding: 40px; }
</style>
<!-- ✅ Non-critical CSS загружаем асинхронно -->
<link rel="preload" href="full-styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="full-styles.css"></noscript>

По умолчанию JS останавливает HTML-парсинг.

<!-- ❌ Блокирует HTML парсинг -->
<script src="app.js"></script>
<!-- ✅ Defer — выполняется после парсинга HTML -->
<script src="app.js" defer></script>
<!-- ✅ Async — загружается параллельно, выполняется сразу -->
<script src="analytics.js" async></script>
<!-- ✅ Module — автоматически defer -->
<script type="module" src="app.js"></script>

Разница defer vs async:

  • defer — сохраняет порядок, выполняется после DOM готов
  • async — не гарантирует порядок, для независимых скриптов
<!-- Предзагрузка критических ресурсов -->
<link rel="preload" href="/hero.webp" as="image" fetchpriority="high">
<link rel="preload" href="/main.css" as="style">
<link rel="preload" href="/font.woff2" as="font" type="font/woff2" crossorigin>
<!-- Предподключение к внешним серверам -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://api.example.com" crossorigin>
<!-- Предварительная загрузка следующей страницы -->
<link rel="prefetch" href="/next-page.html">
<link rel="prefetch" href="/next-page-image.jpg" as="image">

Reflow (Layout) — пересчёт размеров и позиций. Дорогая операция.
Repaint — перерисовка пикселей. Дешевле reflow.

// ❌ Вызывает множественные reflow (thrashing)
for (let i = 0; i < 100; i++) {
element.style.left = element.offsetLeft + 1 + 'px'; // read + write = reflow
}
// ✅ Читаем, потом пишем (batch)
const positions = elements.map(el => el.offsetLeft); // все reads
elements.forEach((el, i) => {
el.style.left = positions[i] + 1 + 'px'; // все writes
});
// ✅ Используем transform вместо left/top (не вызывает reflow!)
element.style.transform = 'translateX(100px)';
// ✅ Оффскрин манипуляции
const fragment = document.createDocumentFragment();
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
fragment.appendChild(li);
});
list.appendChild(fragment); // один reflow
  • Изменение width, height, margin, padding, border
  • Добавление/удаление элементов в DOM
  • Изменение текстового контента
  • Изменение класса элемента
  • Чтение layout-свойств: offsetTop, scrollTop, getBoundingClientRect()
  • opacity — только compositing
  • transform — только compositing
  • filter — только compositing
  • will-change: transform — выносит в отдельный GPU слой
/* ✅ Используйте transform для анимаций */
.animated {
transform: translateX(0);
transition: transform 0.3s ease;
}
.animated:hover {
transform: translateX(10px); /* Не вызывает reflow */
}
/* ✅ will-change для GPU слоёв (используйте осторожно!) */
.hero-animation {
will-change: transform, opacity;
}
/* ❌ Не используйте will-change везде — жрёт память */
.bad { will-change: all; }
/* ✅ Containment — изолируйте область рендеринга */
.widget {
contain: layout style paint;
}