Последние годы мы видим в вебе два тренда: 1. Веб-страницы значительно выросли, особенно в плане запросов по требуемой полосе пропускания и загружаемым ресурсам (стили, скрипты, изображения, шрифты). 2. Выросло количество пользователей, просматривающих сайты с мобильных телефонов и планшетов.

Дома у большинства есть ПК и быстрый интернет. Но ничуть не меньше пользователей, находящихся в иных условиях. Даже быстрое соединение 4G в дороге решает не все проблемы — остается проблема с экономией трафика и скоростью рендеринга на мобильных устройствах.

Все это ведет к новому отношению к скорости загрузки страниц и их оптимизации. Google повышает в выдаче страницы, грузящиеся быстрее секунды. 1 секунда это принятое оптимальное время ответа страницы (10 мс это мгновенно, 3 секунды заставляют отменить загрузку сайта, а 10 секунд ожидания могут навсегда отпугнуть посетителей).

Но как этого достигнуть, не урезая функциональность и не ухудшая внешний вид?

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

От загрузки страницы до рендеринга

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

Это совершенно меняет понятие “скорость загрузки страницы”. Недостаточно просто быстро загрузить страницу — решающее значение, особенно для пользователей мобильных устройств, имеет скорость появления визуального отображения страницы на экране. Важен именно критический путь — так как далеко не весь код на странице производит что-либо в окне браузера.

Для оптимизации критического пути рендеринга и, соответственно, скорости рендеринга, давайте рассмотрим внимательней шаги по рендерингу страницы и их взаимодействие.

От чистого листа до содержания

Представьте себя на месте пользователя, заходящего на сайт. После ввода URL, браузер отправляет запрос на сервер и получает в качестве ответа следующий HTML:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Sample Site</title>
    <link href="style.css" rel="stylesheet">
  </head>
  <body>
    <p>
      Hello <span>there</span> SitePoint!
      <img src="photo.jpg">
    </p>
  </body>
</html>

Браузер парсит этот поток байтов кода в объектную модель документа (DOM). DOM это полное древовидное представление HTML-разметки:

DOM model

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

На этой точке окно браузера пустое. Обратите внимание, как обрабатываются CSS, подключенные из <head>. Применение стилей это один из важнейших моментов рендеринга, поэтому стили должны загружаться сразу как только парсер дойдет до тега <link>.

p { font-weight: bold; }
p span { display: none; }

Этот небольшой фрагмент CSS парсится в объектную модель CSS или CSSOM.

CSSOM

К сожалению, CSSOM не может строится постепенно, как DOM. Представьте, что в нашей таблице стилей, приведенной выше есть третья строка, например p { font-weight: normal; }, переписывающая первую декларацию. И именно по причине каскадирования и переписывания, мы должны дождаться полной загрузки CSS, перед тем как перейти к рендерингу. Пока CSS не загружен — рендеринг блокирован.

Как только браузер получает машинно-читаемое представление разметки и стилей, он переходит к построению дерева рендеринга — структуры, сочетающей DOM и CSSOM для всех видимых элементов:

DOM и CSSOM

Отметьте, что тег span не является частью дерева рендеринга (еще одна причина, по которой рендеринг не начинается без CSS — только в CSS есть информация об отображении/не отображении элементов на странице).

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

Каждый раз, когда дерево рендеринга меняется (при использовании интерактивного JS, например) или меняется область просмотра (путем изменения размеров браузера или поворота мобильного устройства), запускается отрисовка.

Полностью критический путь рендеринга для нашего простого примера выглядят так:

критический путь рендеринга

Как насчет изображений

Фактически только HTML, CSS и JavaScript критически важны для рендеринга (это ресурсы, загрузка которых блокирует рендеринг страницы). Как это не удивительно, изображения не блокируют ни построение DOM, ни первоначальный рендеринг страницы. Посмотрите на вкладку сеть в инструментах разработчика Chrome:

Chrome DevTools

Синяя вертикальная полоса это событие DOMContentLoaded, запускающееся после построения DOM. Изображения загружаются позже и поэтому не блокируют парсинг. Они блокируют событие Load (красная вертикальная полоса) — это событие означает, что все необходимые для страницы ресурсы скачаны и обработаны. Изображения, конечно, необходимо оптимизировать — но они не являются частью критического пути рендеринга.

А теперь JavaScript

JavaScript это мощный инструмент и он имеет огромное значение для критического пути. Давайте добавим в наш параграф небольшой скрипт:

<p>
  Hello <span>there</span>, SitePoint!
  <script>
    document.write('How are you?');
    var color = elem.style.color;
    elem.style.color = 'red';
  </script>
  <img src="photo.jpg">
</p>

Даже этот простой фрагмент кода демонстрирует, что скрипты могут изменять и DOM, и CSSOM. Так как JavaScript может добавлять узлы в DOM, парсер останавливает свою работу, пока скрипты не будут выполнены. JavaScript блокирует парсер.

JS в примере запрашивает цвет элемента, а это значит, что CSSOM должна быть готова до запуска скрипта. CSS должен быть загружен до скриптов.

Давайте оценим критический путь после избавления от инлайнового скрипта и замены его ссылкой на внешний файл JavaScript:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Sample Site</title>
    <link href="style.css" rel="stylesheet">
  </head>
  <body>
    <p>
      Hello <span>there</span> SitePoint!
      <img src="photo.jpg">
    </p>
    <script src="app.js"></script>
  </body>
</html>

критический путь после оптимизации

Внешний файл JavaScript требует дополнительный запрос — инлайновый скрипт обходится без этого. Также отметьте — неважно какой файл, CSS и JS, будет загружен первым, парсинг CSS всегда начинается раньше. Если CSSOM построена, тогда уже выполняется скрипт. И только после этого парсер DOM разблокируется и завершает работу. Даже в простом сайте из нашего примера, находятся свои блокираторы, вмешивающиеся в процесс рендеринга.

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

Три шага для оптимизации критического пути рендеринга

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

1. Минимизируйте объем трафика, передающегося на сайт.

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

Минифицируйте, сжимайте и кэшируйте ресурсы вашего сайта, а также его разметку. Да, HTML также блокирует рендеринг. Многие системы сборки (наиболее известные — Grunt и Gulp) поддерживают плагины минификации и очистки от лишних пробелов и комментариев.

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

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

2. Минимизируйте блокирование рендеринга загрузкой CSS

Обработка CSS это важная часть процесса рендеринга. CSS блокирует не только рендеринг, но и выполнение скриптов. Поэтому совершенно очевидно главное правило по загрузке: максимально быстро — подключаем link со стилями в head. Также можно уменьшить размер CSS с помощью медиа-запросов. Например, для нашего сайта есть стандартные стили, стили для портретной ориентации и стили для печати. Имеет смысл разбить код на несколько файлов и подключать их в случае необходимости:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Sample Site</title>
    <link href="style.css" rel="stylesheet">
    <link href="print.css" rel="stylesheet" media="print">
    <link href="landscape.css" rel="stylesheet" media="orientation:landscape">
  </head>
  ...

Основной файл стилей уменьшится, а значит уменьшится и время блокировки рендеринга. Стили для печати будут использоваться только при печати и не будут загружаться в остальных случаях. Файл CSS для портретной ориентации будет загружаться же только при повороте мобильного устройства. Очевидно, что мы можем использовать медиа-запросы, чтобы отдельно загружать стили, необходимые только в особых ситуациях. Это уменьшает размер подключаемого блокирующего CSS, а значит, уменьшает и время, затрачиваемое браузером на парсинг. Примечание: Браузер по прежнему будет загружать дополнительные стили, но это будет происходить в низком приоритете, параллельно процессу рендеринга. Также в отдельных ситуациях можно инлайнировать блокирующий CSS — это позволяет сэкономить запросы и ограничить браузер парсингом HTML.

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Sample Site</title>
    <style>
      p { font-weight: bold; }
      p span { display: none; }
    </style>
    <!-- No link tag needed! -->
  </head>
  ...

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

Есть несколько автоматических инструментов для оптимизации CSS:

3. Минимизируйте блокирующий рендеринг JavaScript

Точно так же как и CSS, JavaScript можно включать напрямую в разметку, чтобы сэкономить на сетевых запросах.

В лучшем случае вы не подключаете JS до первоначальной загрузки страницы. Если это не возможно, попытайтесь отложить выполнение JS до события загрузки.

Также внимательно проверяйте свои и чужие скрипты. Если они не взаимодействуют с DOM или CSSOM, вы можете загрузить их асинхронно. Это делается просто:

  ...
    <script src="app.js" async></script>
  </body>
</html>

С атрибутом async вы говорите браузеру, что необязательно немедленно исполнять скрипт по ссылке из HTML. Браузер строит DOM и только после построения DOM запускает выполнение скрипта. Представьте скрипт app.js, взаимодействующий только с Гугл-аналитикой и социальными сетями — если это скрипт не трогает DOM или CSSOM, он отличный кандидат на асинхронную загрузку.

Заключение

Большая часть статьи почерпнута из следующих источников:

Основные тезисы:

  • Понятие скорости загрузки страницы изменилось — это не просто скорость загрузки, это скорость рендеринга.
  • К критическим участкам рендеринга относятся все шаги по превращению ресурсов в картинку в браузере:DOM и CSSOM, JavaScript, дерево рендеринга, макетирование и отрисовка.
  • HTML блокирует рендеринг, но DOM может строиться постепенно.
  • CSS блокирует рендеринг и выполнение скриптов, его можно аккуратно оптимизировать с помощью медиа-запросов и инлайнирования.
  • JS блокирует парсинг, осторожно используйте его до стадии первоначальной загрузки, отложите выполнение или загружайте асинхронно.
  • Не забывайте про размер — минифицируйте, сжимайте, кэшируйте.

Активней пользуйтесь инструментами, такими как Chrome DevTools, PageSpeed Insights или WebPagetest, чтобы понять, какие оптимизации возможны и необходимы.

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