Урок по треку Браузер и DOM 7 мин

Как браузер рисует страницу

Критический путь: DOM, CSSOM, layout, paint, composite. Reflow, repaint и блокирующие ресурсы.

Между получением HTML и картинкой на экране браузер проходит цепочку шагов. Её называют критическим путём рендеринга (critical rendering path). Понимая её, легко объяснить, почему страница грузится медленно и почему анимация дёргается.

Конвейер

  1. Парсинг HTML → DOM. Парсер читает байты по мере загрузки и строит дерево.
  2. Парсинг CSS → CSSOM. Все стили (внешние, <style>, инлайн) собираются в дерево правил.
  3. Style. Для каждого элемента вычисляются итоговые стили. Элементы с display: none в дальнейший рендер не попадают (в классическом описании DOM + CSSOM дают render tree).
  4. Layout (reflow). Считаются размеры и позиции всех блоков.
  5. Paint. Блоки превращаются в команды рисования: фон, текст, границы, тени.
  6. Composite. Нарисованные слои складываются в итоговый кадр, это делает отдельный поток композитора, часто на GPU.

Что блокирует

  • CSS блокирует рендер. Пока не загружен и не разобран CSS из <head>, браузер ничего не покажет, иначе пользователь увидит вспышку нестилизованного контента.
  • Обычный <script> блокирует парсинг. Парсер останавливается, ждёт загрузки и выполнения скрипта: скрипт может вызвать document.write или читать DOM. А если перед скриптом стоит ещё не загруженный CSS, скрипт ждёт и его, ведь он может читать стили.

Варианты подключения скриптов:

АтрибутЗагрузкаВыполнениеПорядок
нетблокирует парсерсразупо порядку
asyncпараллельнокак только скачалсялюбой
deferпараллельнопосле парсинга, до DOMContentLoadedпо порядку
type="module"параллельнокак defer по умолчаниюпо порядку

Отдельно работает preload scanner: пока главный парсер стоит на скрипте, он просматривает HTML дальше и заранее начинает качать картинки, стили и скрипты.

DOMContentLoaded и load

  • DOMContentLoaded - HTML разобран, DOM построен, defer-скрипты выполнены. Картинки могут ещё грузиться.
  • load - загружено всё: картинки, стили, iframe.

Reflow, repaint, composite

Изменение стилей запускает конвейер с какого-то шага:

  • Меняем геометрию (width, top, font-size, добавили элемент) → layout, paint, composite. Самое дорогое.
  • Меняем только вид (color, background, box-shadow) → paint и composite.
  • Меняем transform или opacity у элемента на своём слое → только composite. Это дёшево и не зависит от главного потока, поэтому анимации делают именно ими.

Принудительный синхронный layout

Браузер копит изменения и пересчитывает layout перед кадром. Но если после записи стиля прочитать геометрию (offsetHeight, getBoundingClientRect(), scrollTop, getComputedStyle), ему придётся посчитать layout прямо сейчас.

// плохо: чтение и запись чередуются, layout на каждой итерации
for (const el of items) {
  el.style.width = box.offsetWidth + 'px';
}

// хорошо: сначала прочитали, потом записали
const w = box.offsetWidth;
for (const el of items) el.style.width = w + 'px';

Чередование чтений и записей в цикле называют layout thrashing.

requestAnimationFrame

requestAnimationFrame(cb) вызывает функцию перед следующей отрисовкой кадра. Подходит для анимаций на JS и для пакетной записи стилей. В фоновой вкладке колбэки ставятся на паузу.

Частые ошибки

  • Анимировать top/left/width вместо transform.
  • Читать offsetHeight в цикле после изменения стилей.
  • Ставить тяжёлые скрипты в <head> без defer.
  • Думать, что async сохраняет порядок скриптов.

Что спросят на собесе

  • Этапы рендеринга страницы от HTML до пикселей.
  • Чем async отличается от defer.
  • Reflow и repaint: что вызывает каждый и что дешевле.
  • Почему анимировать transform лучше, чем left.
  • Что такое layout thrashing и как его избежать.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.