Первая часть.

Компоновка макета

После того как браузер создаст DOM и CSSOM, он переходит к компоновке макета. Этот этап очень важен, поскольку определяет, где элементы появятся на экране. Для каждого элемента рассчитывается размер и положение на основе области просмотра окна браузера и свойств CSS.

Этап компоновки макета часто требует больших затрат процессора, особенно при загрузке сложных веб-страниц или при изменении размера области просмотра (например, при повороте телефона или изменении размера окна браузера), что приводит к пересчету. 

Посмотрим, как происходит компоновка макета, и ознакомимся с лучшими практиками для ее оптимизации.

Установка области просмотра с помощью тега <meta> 

Тег области просмотра (viewport) <meta> играет важную роль в определении макета, особенно на мобильных устройствах. Вот как он работает:

<meta name="viewport" content="width=device-width">

Устанавливая значение width=device-width, вы даете браузеру указание привести ширину макета в соответствие с реальной шириной экрана устройства. Это важно для мобильной отзывчивости, так как адаптирует макет к ширине экрана.

  • Без тега <meta>: браузер по умолчанию устанавливает ширину макета 980px, из-за чего страница может выглядеть увеличенной и трудночитаемой на мобильных устройствах.
  • С тегом <meta>: макет адаптируется к ширине устройства, обеспечивая правильный размер и расположение элементов на экране.

Расчет макета

Как уже было сказано, на этапе компоновки макета браузер рассчитывает точные размеры и положение каждого элемента на странице. При этом учитываются:

  • Ширина области просмотра: задается тегом <meta>.
  • Свойства CSS: такие как  widthheightpaddingmargin и позиционирование: absoluterelative и т. д.
  • Контент: содержимое элементов (например, изображений, текста) влияет на расчеты макета, особенно если размер содержимого меняется динамически.

Рассмотрим для примера компоновку макета на мобильном устройстве шириной 360px:

  • Если задать элементу div значение width: 100%, он будет занимать всю ширину экрана устройства (360px).
  • Однако если тег области просмотра <meta> отсутствует, браузер может предположить ширину 980px, что приведет к уменьшению размера.

Пересчет макета

При повороте телефона или изменении размера браузера макет пересчитывается, чтобы адаптироваться к новому размеру области просмотра. Этот пересчет макета влияет на:

  • позиционирование и изменение размеров всех элементов на странице;
  • отзывчивые элементы: элементы с относительными единицами измерения (%vwvh и т. д.) пересчитываются в соответствии с новыми размерами экрана.

На этапе компоновки макета определяется ширина в пикселях каждого элемента в дереве рендеринга:

Пример: пересчет макета при повороте

Первоначальный вид (портретный режим):

  • Предположим, что ширина области просмотра составляет 360px, а для изображения установлено значение width: 100%.
  • Изображение занимает 360px.

Повернутый вид (ландшафтный режим):

  • При повороте область просмотра может увеличиться до 640px.
  • Размер изображения пересчитывается до width: 640px, чтобы соответствовать новой области просмотра.

Пересчет макета может стать узким местом в производительности, если задействовано много элементов, поскольку каждый из них должен быть перепозиционирован и изменен в размерах.

Измерение производительности макета

Производительность Medium.com

Производительность макета можно измерить с помощью таких инструментов, как Chrome DevTools, которые отображают время, затрачиваемое на каждый расчет макета. Например:

  • Время, затрачиваемое на расчет макета сайта Medium.com (опредленное с помощью Chrome DevTools), составляет 171,3 мс.
  • Это время показывает, как долго браузер определял положение и размер элементов в области просмотра.

Повторные расчеты макета могут замедлить работу пользователя, особенно при частом обновлении элементов.

Оптимизация макета для повышения производительности

Поскольку пересчеты макета являются дорогостоящими, их минимизация может повысить производительность. Вот несколько эффективных методов:

1. Пакетные изменения DOM и стилей

Любое обновление DOM (например, изменение стилей или добавление/удаление элементов) может привести к пересчету макета. Чтобы свести это к минимуму, при каждом возможном случае вносятся пакетные изменения DOM.

// Пример пакетного внесения изменений, влияющих на макет
let element = document.getElementById("box");

// Изменяйте несколько стилей одновременно, чтобы минимизировать пересчеты
element.style.width = "200px";
element.style.height = "200px";

2. Избегание операционной перегрузки макета

Операционная перегрузка макета (Layout Thrashing) происходит, когда JavaScript многократно выполняет чтение и запись в DOM в цикле, что приводит к быстрому запуску нескольких макетов подряд. Это может произойти, когда вы считываете свойство макета (например, offsetWidth) сразу после его установки, что приводит к повторному вычислению браузером.

// Плохой пример: приводит к перегрузке макета
for (let i = 0; i < 100; i++) {
  let width = element.offsetWidth; // Считывание свойства макета
  element.style.width = width + 10 + "px"; // Запись свойства макета
}

// Хороший пример: пакетные чтение и запись
let width = element.offsetWidth;
for (let i = 0; i < 100; i++) {
  width += 10;
}
element.style.width = width + "px";

3. Использование CSS-преобразований для анимации

CSS-преобразования (например, transform: translateX() или transform: scale()) часто обрабатываются GPU (графическим процессором), а не CPU (центральным процессором), что делает их более эффективными в плане анимации, чем такие свойства, как width и height, которые запускают перерасчет макета.

/* Использование трансформаций для анимации */
.move-box {
  transform: translateX(100px);
}

4. Оптимизация адаптивных изображений

Используйте адаптивные изображения (srcsetsizes), чтобы избежать изменения размера больших изображений в CSS или JavaScript. Загрузка изображений, соответствующих области просмотра, уменьшает необходимость в пересчетах макета с учетом различных размеров экрана.

<img src="small.jpg" srcset="large.jpg 1024w, medium.jpg 768w, small.jpg 360w" sizes="(max-width: 600px) 100vw, 50vw">

5. Избегание принудительной синхронизации

Принудительная синхронизация макетов происходит, когда JavaScript требует от браузера расчета макета непосредственно перед выполнением дальнейших изменений. Это может привести к сбоям в работе. Используйте requestAnimationFrame(), чтобы отложить обновления до следующего цикла рендеринга.

// Пример использования requestAnimationFrame для предотвращения принудительного расчета макета
requestAnimationFrame(() => {
  element.style.width = "100px";
});

6. Уменьшение сложности CSS-селекторов

Сложные CSS-селекторы могут замедлить расчет макета. По возможности используйте простые селекторы, чтобы ускорить процесс рендеринга.

Пример сценария: повышение производительности макета

Рассмотрим сценарий, в котором карусель изображений обновляет содержимое по мере прокрутки. Каждое новое изображение загружается и стилизуется динамически. Если при каждой вставке изображения или изменении стиля выполняется перерасчет макета, карусель может работать медленно. Избежать этого помогут следующие меры:

  1. Пакетное обновление стилей: обновить стили всех изображений необходимо за один раз, а не по отдельности.
  1. Использование CSS-преобразований: вместо обновления позиции по отношению к left или top, используйте transform: translateX() для плавной анимации, оптимизированной для графического процессора.

Частые перерасчеты макета могут увеличить TBT, замедляя скорость отклика и нанося ущерб FID.

Советы по сокращению TBT и оптимизации FID:

  1. Вносите пакетные изменения DOM и стилей для минимизации перерасчетов макета.
  1. Избегайте операционной перегрузки макета, возникающей из-за разделения операций чтения и записи в JavaScript.
  1. Используйте requestAnimationFrame() для более плавного обновления.

Этап отрисовки

Как только этап компоновки макета завершен и браузеру известны положение и размеры каждого элемента, он переходит к этапу отрисовки. На этом этапе браузер заполняет пиксели для каждого элемента на основе вычисленных стилей (таких как цвета, границы, тени и фон). Этап отрисовки важен для визуального оформления страницы, преобразования структуры макета в реальный визуальный результат, который видят пользователи.

В чем заключается процесс отрисовки

На этом этапе каждый элемент на странице отрисовывается в несколько слоев, которые затем объединяются в единое конечное изображение на экране. Вот краткое описание процесса отрисовки:

  1. Создание слоев. Элементы объединяются в слои. Например, элемент с z-index, эффектом 3D-преобразования или CSS-фильтра может быть перенесен на отдельный слой для повышения производительности рендеринга.
  1. Растеризация. Каждый слой разбивается на фрагменты, и браузер «растрирует» каждый фрагмент, заполняя его цветом, изображениями, тенями и другими стилями. Растеризация может занимать много времени на CPU или GPU в зависимости от сложности.
  2. Операции отрисовки. К ним относятся заливка фона, выведение текста, наложение границ, а также рендеринг теней и градиентов.

Триггеры для перерисовки

Перерисовка происходит, когда меняются визуальные свойства, но макет остается неизменным. Например, изменение цвета фона элемента приведет к перерисовке без пересчета макета. Вот общие свойства, которые вызывают перерисовку:

  • цветовые изменения: цвет фона, цвет рамки, цвет текста и т. д.;
  • визуальные эффекты: тени, непрозрачность и преобразования, которые не влияют на размеры макета;
  • CSS-анимация: любое визуальное изменение (например, цвета или непрозрачности) в анимации приводит к перерисовке.

Пример

Рассмотрим элемент, заданный в следующем стиле:

.box {
  width: 200px;
  height: 200px;
  background-color: blue;
}

Изменим его фоновый цвет на красный:

document.querySelector(".box").style.backgroundColor = "red";

Это изменение запускает перерисовку, поскольку изменение цвета влияет только на пиксели в существующей структуре макета.

Методы оптимизации критически важного пути рендеринга (CRP)

Напомню, что критически важный путь рендеринга (CRP) — последовательность шагов, выполняемых браузером для рендеринга веб-страницы. Оптимизация CRP необходима для улучшения показателей веб-производительности, таких как скорость появления первого элемента контента (FCP) и скорость появления крупнейшего элемента контента (LCP). В этой части статьи поговорим о нарушителях CRP (DOM, CSSOM, JavaScript) и рассмотрим практические методы оптимизации, позволяющие сократить длительность CRP и повысить производительность рендеринга.

DOM и CSSOM: нарушители CRP

DOM (объектная модель документа) и CSSOM (объектная модель CSS) имеют решающее значение для рендеринга. Однако обе эти модели могут блокировать рендеринг, задерживая просмотр значимого контента пользователями.

Минимизация, сжатие и кэширование

Минимизация:

  • Удалите ненужные комментарии, пробелы и неиспользуемый код в HTML, CSS и JavaScript.
  • Пример:
/* До */
body {
  font-size: 16px; /* Main font size */
}

/* После */
body {font-size:16px;}

Сжатие:

  • Используйте алгоритмы сжатия Gzip или Brotli, чтобы уменьшить размер передаваемых ресурсов.
  • Пример: CSS-файл размером 100 КБАЙТ можно сжать до ~30 КБАЙТ с помощью Gzip.

Кэширование:

Используйте кэширование браузера, устанавливая заголовки кэширования для статических активов, чтобы избежать избыточных загрузок.

Разблокировка CSS

CSS — ресурс, блокирующий рендеринг, поскольку браузер не может рендерить контент, пока полностью не распарсит CSS. Разблокировать CSS помогут следующие оптимизации:

Медиа-запросы для условной загрузки

  • Разделите CSS на файлы меньшего размера в зависимости от медиа-условий, чтобы загружать только соответствующие стили.
  • Пример:
<!-- Общие стили -->
<link href="style.css" rel="stylesheet">
<style>
  body {font-size: 16px;}
</style>

<!-- Стили, характерные для конкретной отрисовки -->
<link href="print.css" rel="stylesheet" media="print">

Критически важные CSS

  • Используйте встроенные критически важные CSS для видимого без прокрутки контента, чтобы уменьшить блокировку рендеринга.
  • Пример:
<style>
body {font-size: 16px; color: black;}
</style>

JavaScript и CRP

JavaScript может манипулировать как DOM, так и CSSOM, но также вводит блокирующее поведение во время парсинга и выполнения.

Поведение, блокирующее парсинг

JavaScript блокирует создание DOM. Например:

<p>
  Medium
  <script>
    document.write("articles");
  </script>
  are awesome.
</p>

Процесс:

  1. Браузер начинает выполнять парсинг DOM и обнаруживает <script>.
  1. Он приостанавливает создание DOM для выполнения JavaScript.
  1. JavaScript записывает «articles» перед возобновлением создания DOM.

Представление в виде схемы:

Build DOM → (GET write.js) → Response → Run JS → Resume DOM

Способы оптимизации выполнения JavaScript

Атрибут defer

  • Используйте defer для выполнения несущественных скриптов после полного создания DOM.
  • Пример:
<script defer src="app.js"></script>

Атрибут async

  • Используйте async для скриптов, которые не зависят от других ресурсов.
  • Пример:
<script async src="analytics.js"></script>

Встраивание небольших скриптов

Встраивание критически важных скриптов позволяет избежать дополнительных сетевых запросов к небольшим файлам JavaScript.

<script>
console.log("Hello, world!");
</script>

Реальные сценарии

Сценарий 1-й. Использование скриптов: асинхронного/встроенного/блокирующего  

Асинхронный (async) скрипт:

  • Извлекает и выполняет JavaScript, не блокируя CSS.

Встроенный (inline) скрипт:

  • Блокирует рендеринг, но позволяет избежать дополнительного запроса.

Блокирующий (blocking) скрипт:

  • Приостанавливает создание как DOM, так и CSSOM.

Сценарий 2-й. Сокращение длины CRP 

  • В лучшем случае для создания небольшого HTML-файла с минимальным количеством критических ресурсов требуется только один циклический проход CRP (round trip).
  • В худшем случае большие HTML-файлы и зависимые ресурсы увеличивают количество циклических проходов и продолжительность CRP.

Общие стратегии оптимизации критически важного пути рендеринга (CRP)

Чтобы ускорить загрузку страницы, оптимизация критически важного пути рендеринга включает в себя три основные задачи:

  1. Минимизация байтов: уменьшение размера ресурсов, передаваемых по сети.
  1. Сокращение критически важных ресурсов: ограничение количества ресурсов, необходимых для рендеринга.
  1. Сокращение длины CRP: сокращение количества сетевых циклических проходов, необходимых для получения критически важных ресурсов.

Важные моменты, о которых нужно помнить

1. Встраивайте критически важные CSS и откладывайте загрузку остальных таблиц стилей:

  • Встроенные стили анализируются с помощью HTML, что ускоряет рендеринг.
  • Большие, некритически важные таблицы стилей могут быть отложены или загружены асинхронно.

2. Откладывайте загрузку несущественных элементов:

  • Используйте отложенную загрузку для изображений и некритически важного JavaScript.

3. Используйте предварительную загрузку и предварительное подключение:

  • Используйте <link rel="preload"> для высокоприоритетных ресурсов.
  • Используйте <link rel="preconnect"> для установки ранних подключений к внешним доменам.

CRP-матрица

CRP-матрица — мощный инструмент для анализа эффективности процесса рендеринга при оптимизации веб-производительности. Она помогает выявить ключевые узкие места и направляет разработчиков к практическим улучшениям. Далее подробно разберем компоненты матрицы CRP, приведем примеры и рассмотрим, как оптимизация влияет на ее значения.

Ключевые показатели CRP-матрицы

1. Количество критически необходимых ресурсов:

  • Это ресурсы, которые браузер должен загрузить и обработать для отображения страницы (например, HTML, CSS).
  • Некритически важные ресурсы, такие как изображения и асинхронный JavaScript, не учитываются, поскольку не блокируют рендеринг.

2. Общее количество критически необходимых килобайтов:

  • Это суммарный размер всех критически важных ресурсов.
  • Меньшие размеры приводят к более быстрой загрузке, сокращая время, необходимое для создания DOM и CSSOM.

3. Минимальная длина CRP (количество циклических проходов из конца в конец и обратно):

  • Количество сетевых циклических проходов, необходимых для получения всех критически важных ресурсов.
  • Каждый циклический проход приводит к задержке, поэтому сокращение количества циклических проходов повышает производительность.

Пример пошагового руководства по работе с CRP-матрицей 

Рассмотрим сценарий рендеринга базовой веб-страницы, которая содержит:

  • HTML: 5 КБ (критично);
  • CSS: 4 КБ (критично);
  • изображения: 10 КБ (некритично);
  • JavaScript: 2 КБ (некритично, если отложенная загрузка).

CRP-матрица (без оптимизации):

  1. Количество критически необходимых ресурсов: 2 (HTML + CSS).
  1. Общее количество критически необходимых килобайтов: 9 КБ (5 КБ HTML + 4 КБ CSS).
  1. Минимальная длина CRP: 2 циклических прохода:
  • первый циклический проход: получение HTML;
  • второй циклический проход: получение CSS.

Оптимизация CRP

Теперь применим стратегии оптимизации к этой веб-странице:

1. Встроим критический CSS:

  • Переместим необходимые CSS в тег <style> в HTML, что уменьшит необходимость в отдельном запросе CSS.
  • Загрузку оставшихся CSS отложим или выполним с помощью мультимедийных запросов.

2. Отложим загрузку JavaScript:

  • Используем атрибуты defer или async  для загрузки JavaScript после создания DOM.

CRP-матрица (после оптимизации):

  1. Количество критически необходимых ресурсов: 1 (только HTML, поскольку CSS встроен).
  1. Общее количество критически необходимых килобайтов: 5 КБ (только HTML).
  1. Минимальная длина CRP: 1 циклический проход (только HTML).

Продвинутые технологии оптимизации CRP

1. Инструменты для извлечения важных CSS-файлов

  • Плагины Webpack, Gulp или специализированные библиотеки могут автоматизировать извлечение важных CSS-файлов для сложных сайтов.

2. Рендеринг на стороне сервера (SSR)

  • Фреймворки, такие как Next.js (React), Nuxt.js (Vue) или Angular Universal, могут рендерить HTML на сервере.
  • Пользователь сразу видит предварительно отрендеренный HTML, что сокращает время на первый рендеринг и улучшает SEO.

3. Разделение кода

  • Разделение больших пакетов JavaScript позволяет изначально загрузить только необходимый код.
  • Технология популярна в системах сборки React, Vue или Angular, позволяющих уменьшить нагрузку на JS при первой загрузке.

4. Подсказки по ресурсам

  • rel="dns-prefetch": ускоряет разрешение доменных имен для внешних ресурсов;
  • rel="preconnect": сокращает время на установление сетевого подключения к внешним доменам.

5. HTTP/2 и более поздние версии

  • С помощью HTTP/2 можно получать несколько ресурсов по одному TCP-соединению, что сокращает некоторые накладные расходы;
  • Использование функции server push (если поддерживается) позволяет выполнить упреждающую отправку критически важных ресурсов.

5. Оптимизация изображений

  • Хотя CSS и JS часто оказываются в центре внимания, большие изображения также могут увеличить время обработки.
  • Рекомендуется использовать адаптивные изображения (<img srcset>), современные форматы (WebP, AVIF) и отложенную загрузку для изображений, не выводящихся на экран.

Заключение

Оптимизация CRP — не просто сокращение времени загрузки, это обеспечение более плавного и отзывчивого взаимодействия с пользователем. Минимизация количества критически важных ресурсов, уменьшение их размера и сокращение длины CRP позволяют создавать высокопроизводительные сайты. 

Чтобы ваш сайт был именно таким, необходимо:

  • Сосредоточиться на первой отрисовке: убедитесь, что браузер может отображать наиболее важные части вашей страницы без задержек.
  • Выполнять сокращение, объединение и встраивание файлов: меньшее количество файлов меньшего размера означает меньшее количество циклических проходов и более быстрый рендеринг.
  • Использовать асинхронную загрузку: маркируйте скрипты и стили, предотвращая блокировку парсинга.
  • Тестировать и оптимизировать результаты: используйте Lighthouse, WebPageTest или встроенные в ваш браузер инструменты для количественной оценки улучшений и дальнейшей оптимизации.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Saquib Khan: How to Build Websites That Load Before You Blink: Frontend Optimization Tips

Предыдущая статья8 эффективных способов построения доверительных отношений с коллегами
Следующая статьяКак решить реальную задачу при помощи структурированной конкурентности и виртуальных потоков Java 21