Missed the action at this year's Chrome Dev Summit? Catch up with our playlist on YouTube. Watch now.

Анализ процесса визуализации

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

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

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

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

  • соединение (вызов сервера и получение ответа) - 100 мс;
  • ответ сервера - 100 мс для HTML-документов и 10 мс для других файлов.

Страница Hello World

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Critical Path: No Style</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
  </body>
</html>

Начнем с самой простой страницы без CSS и JavaScript, состоящей из HTML-разметки и картинки. Запустим инструменты разработчика в Chrome, откроем вкладку Network (Сеть) и проанализируем динамический список:

Как и ожидалось, на скачивание HTML-файла ушло примерно 200 мс. Обратите внимание, что светло-голубая полоса отражает время ожидания, а темно-голубая - период скачивания файла с момента получения первых байтов. Размер HTML-файла очень маленький, поэтому для скачивания потребовалось всего одно соединение продолжительностью 200 мс, т. е. по 100 мс на ожидание и получение ответа.

Скачав HTML-документ, браузер анализирует байты, преобразует их в объекты и строит модель визуализации. Обратите внимание, что в инструментах разработчика также указана продолжительность события DOMContentLoaded (216 мс), отмеченного на графике синей вертикальной чертой. Пробел между этой чертой и темно-голубой полосой - это время, которое понадобилось браузеру для создания модели визуализации (всего пара миллисекунд в нашем случае).

На графике есть ещё одна любопытная деталь: картинка awesome photo не помешала событию DOMContentLoaded. Значит, браузер может создавать модель визуализации и выводить страницу на экран, не дожидаясь загрузки всех элементов. Отсюда вывод: не все ресурсы необходимы для первоочередной визуализации. Первоочередной процесс визуализации затрагивает только HTML-, CSS и JavaScript-файлы. Изображения не препятствуют выводу страницы, однако мы должны позаботиться о том, чтобы они также загружались достаточно быстро.

До тех пор пока картинки не отобразятся на экране, невозможна полная загрузка страницы и использование события onload. Помните, что страница загружается полностью только после скачивания и обработки всех ресурсов. На скриншоте выше загрузка (load) завершилась через 335 мс - это событие отмечено красной вертикальной чертой.

Страница с CSS и JavaScript

Несмотря на простоту нашего примера, браузеру пришлось потрудиться. Однако обычно страницы содержат не только только HTML-разметку, но и таблицы стилей (CSS), и интерактивные функции (скрипты). Попробуем добавить их в код Hello World:

<html>
  <head>
    <title>Critical Path: Measure Script</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
  </head>
  <body onload="measureCRP()">
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
    <script src="timing.js"></script>
  </body>
</html>

Перед добавлением JavaScript и CSS:

DOM

После добавления JavaScript и CSS

DOM, CSSOM, JavaScript

Браузер обработал CSS- и JavaScript-файлы одновременно, и в динамическом списке появились две новые строки. Но это ещё не все. Вы наверняка заметили, что промежуток между красной и синей чертами сократился. Что произошло?

  • В отличие от первого примера, браузеру нужно скачать и проанализировать CSS-файл, а также построить модель CSSOM. Без нее создать модель визуализации невозможно.
  • Поскольку для выполнения JavaScript может понадобиться CSSOM, браузер блокирует событие DOMContentLoaded (см. синюю черту) до тех пор, пока не скачает и не проанализирует CSS-файл.

Но как ускорить визуализацию? Может, стоит заменить файл JavaScript встроенным скриптом? Это сложный вопрос. Даже если мы разместим в HTML-документе текст скрипта, браузер должен выполнить его, предварительно создав модель CSSOM. Поэтому встроенный JavaScript также блокирует анализ документа.

Тем не менее, будет ли страница загружаться быстрее, если мы все-таки используем встроенный код? Давайте проверим:

Внешний файл JavaScript:

DOM, CSSOM, JavaScript

Встроенный JavaScript:

DOM, CSSOM, встроенный JavaScript

Итак, количество запросов уменьшилось, но время завершения загрузки (load) не изменилось, как и временная метка события DOMContentLoaded. Почему? Во-первых, как мы уже знаем, встроенный скрипт требует создания CSSOM, что невозможно без приостановки синтаксического анализа. Во-вторых, в прошлый раз браузер скачивал файл JavaScript одновременно с CSS, поэтому устранение одного из запросов не играет роли. В результате встроенный код не решил проблему на нашей странице. Что же делать? Есть ли другой способ оптимизации? Есть, и даже не один.

Например, чтобы предотвратить остановку визуализации, можно перенести код JavaScript в отдельный файл и присвоить тегу script параметр async. Посмотрим, что получилось:

<html>
  <head>
    <title>Critical Path: Measure Async</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
  </head>
  <body onload="measureCRP()">
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
    <script async src="timing.js"></script>
  </body>
</html>

Внешний файл JavaScript (блокирующий визуализацию):

DOM, CSSOM, JavaScript

Внешний файл JavaScript (асинхронный):

DOM, CSSOM, асинхронный JavaScript

Уже лучше! Теперь браузер инициирует событие DOMContentLoaded почти сразу после завершения анализа HTML. Поскольку JavaScript не блокирует анализатор, CSSOM создается одновременно с обработкой скрипта.

Другое возможное решение - встроить в HTML как скрипт, так и CSS:

<html>
  <head>
    <title>Critical Path: Measure Inlined</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <style>
      p { font-weight: bold }
      span { color: red }
      p span { display: none }
      img { float: right }
    </style>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
    <script>
      var span = document.getElementsByTagName('span')[0];
      span.textContent = 'interactive'; // change DOM text content
      span.style.display = 'inline';  // change CSSOM property
      // create a new element, style it, and append it to the DOM
      var loadTime = document.createElement('div');
      loadTime.textContent = 'You loaded this page on: ' + new Date();
      loadTime.style.color = 'blue';
      document.body.appendChild(loadTime);
    </script>
  </body>
</html>

DOM, встроенный CSS и встроенный JavaScript

Обратите внимание, что временные отметки события DOMContentLoaded в текущем и прошлом примере почти не различаются. Хотя теперь браузер затрачивает немного больше времени на обработку HTML-документа, в который встроены JavaScript и CSS, мы избавились от ожидания загрузки скрипта и таблицы стилей.

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

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

Алгоритмы визуализации

Для начала вернемся к самой простой странице без CSS и JavaScript, состоящей только из HTML-разметки. Чтобы визуализировать эту страницу, браузер должен отправить запрос на сервер, скачать HTML-документ, проанализировать его, создать модель DOM и, наконец, вывести страницу на экран:

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Critical Path: No Style</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
  </body>
</html>

Процесс визуализации: DOM

Промежуток между T0 и T1 - это время сетевой и серверной обработки. Если HTML-документ небольшой, то для его загрузки достаточно одного соединения. Большие файлы требуют нескольких соединений из-за особенностей протокола TCP. Эту тему мы рассмотрим в одном из следующих уроков. Итак, для визуализации нашей страницы требуется как минимум одно соединение.

Теперь усложним задачу и добавим внешний CSS-файл:

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
  </body>
</html>

Процесс визуализации: DOM и CSSOM

Для передачи HTML-документа требуется одно соединение, но теперь в разметке содержится ссылка на CSS-файл, который также необходим для вывода страницы на экран. Браузер снова соединяется с сервером и скачивает CSS-файл. В результате для визуализации страницы нужно как минимум два соединения (и больше, если мы имеем дело со сложным CSS- или HTML-файлом).

Остановимся на терминах, с помощью которых можно описать процесс визуализации:

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

Теперь рассмотрим характеристики второй страницы, состоящей из HTML и CSS:

Процесс визуализации: DOM и CSSOM

  • первоочередные ресурсы - 2 файла;
  • продолжительность обработки - 2 соединения минимум;
  • число байтов - 9 КБ.

Поскольку для создания модели визуализации нужны DOM и CSSOM, первоочередными ресурсами являются два файла - HTML и CSS. Сначала браузер скачивает HTML-документ, и только потом - CSS, поэтому для обработки файлов нужно как минимум два соединения. Общий размер ресурсов составляет 9 КБ.

А теперь дополним пример кодом JavaScript:

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
    <script src="app.js"></script>
  </body>
</html>

Мы привели в HTML-документе ссылку на внешний файл app.js. Как нам уже известно, это первоочередной ресурс, который задерживает визуализацию. К тому же скрипт может ссылаться на CSSOM, поэтому для его выполнения браузеру нужно скачать файл style.css и сформировать модель. На это время визуализация также будет приостановлена.

Процесс визуализации: DOM, CSSOM и JavaScript

Однако на вкладке Network (Сеть) в инструментах разработчика можно увидеть следующее: когда браузер обнаруживает файлы CSS и JavaScript, он инициирует оба запроса почти одновременно. Поэтому характеристики страницы выглядят так:

  • первоочередные ресурсы - 3 файла;
  • продолжительность обработки - 2 соединения минимум;
  • число байтов - 11 КБ.

Размер первоочередных ресурсов увеличился до 11 КБ, однако для их обработки браузеру нужно всего два соединения, потому что файлы CSS и JavaScript можно скачивать одновременно. Такой анализ характеристик поможет вам выделить первоочередные ресурсы и понять, как браузер получает их. Но это ещё не все.

Как вы помните, мы можем избежать приостановки визуализации, сообщив браузеру, что JavaScript нужно обработать позже. Для этого мы присвоим скрипту параметр async:

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
    <script src="app.js" async></script>
  </body>
</html>

Процесс визуализации: DOM, CSSOM и асинхронный JavaScript

У асинхронного кода есть ряд преимуществ:

  • Он предотвращает приостановку визуализации, поэтому скрипт больше не считается первоочередным ресурсом.
  • Поскольку браузеру не нужно сразу выполнять скрипт, он может отложить создание модели CSSOM и не блокировать событие DOMContentLoaded.
  • Чем скорее браузер инициирует событие DOMContentLoaded, тем раньше начнется выполнение скрипта.

Результатом нашей оптимизации стала страница с двумя первоочередными ресурсами - файлами HTML и CSS размером 9 КБ. Для их обработки потребуется как минимум два соединения.

Но ещё лучшего результата можно добиться, если CSS необходим только для печати страницы:

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet" media="print">
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
    <script src="app.js" async></script>
  </body>
</html>

Процесс визуализации: DOM, CSS для печати и асинхронный JavaScript

Поскольку файл style.css используется только для печати, браузер пропустит его обработку, чтобы не задерживать визуализацию. Значит, страницу можно вывести на экран уже после того, как браузер построит модель DOM! Таким образом мы сократили число первоочередных ресурсов до одного HTML-документа, для обработки которого понадобится как минимум одно соединение.