【原文】CSS и веб-производительность

JavaScript браузер HTML CSS

Оригинальная ссылка:CSS and Network Performance

Это очень длинная статья, которая всесторонне знакомит с актуальными знаниями о загрузке CSS.Ввиду ограниченного уровня переводчиков, способным студентам рекомендуется читать исходный текст напрямую.В то же время, я также надеюсь, что перевод будет полезен Вам. Спасибо~ Ниже текст:


Благодаря любви меня уже более десяти лет называют волшебником CSS, но в последнее время в блоге не так много статей, связанных с CSS. Затем объедините две основные темы CSS и производительности, чтобы получить статью.

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

Какая самая большая проблема?

Вообще говоря, CSS является ключом к производительности (рендеринга) из-за:

  1. Браузер не будет отображать страницу до тех пор, пока не будет построено дерево визуализации;
  2. Дерево рендеринга состоит из DOM и CSSOM;
  3. DOM является результатом HTML плюс (синхронная) блокировка операций JavaScript (после DOM);
  4. CSSOM — это результат применения правил CSS к DOM;
  5. Сделать JavaScript неблокирующим так же просто, как добавить атрибуты async или defer;
  6. Относительно сложно заставить CSS загружаться асинхронно;
  7. Так что запомните это эмпирическое правило: (в идеале)Время загрузки самой медленной таблицы стилей определяет, сколько времени потребуется для отображения страницы..

Исходя из приведенных выше соображений, нам нужно как можно скорее построить DOM и CSSOM. В целом построение DOM выполняется относительно быстро, и (когда запрашивается страница) первый запрос, на который отвечает сервер, представляет собой HTML-документ. Но в целом CSS существует как вспомогательный ресурс HTML, поэтому создание CSSOM обычно занимает больше времени.

В этом посте я расскажу о том, почему CSS является узким местом в сети (либо для себя, либо для других ресурсов), и как это преодолеть, чтобы сократить критический путь и сократить время ожидания до первого рендеринга.

Используйте критический CSS

Если возможно, самый эффективный способ сократить время ожидания перед рендерингом — использовать паттерн Critical CSS: выяснить стили, необходимые для первого рендеринга (обычно в верхней части сгиба), встроить их в<head>теги, другие стили загружаются асинхронно.

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

Разделить код в зависимости от типа носителя

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

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

Браузеры в основном способны откладывать загрузку файлов CSS, которые пропускают медиа-запросы.

<link rel="stylesheet" href="all.css" />

Если мы поместим весь код CSS в один файл, запрос будет выглядеть так:

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

После разделения медиа-запроса в число файлов CSS:

<link rel="stylesheet" href="all.css" media="all" />
<link rel="stylesheet" href="small.css" media="(min-width: 20em)" />
<link rel="stylesheet" href="medium.css" media="(min-width: 64em)" />
<link rel="stylesheet" href="large.css" media="(min-width: 90em)" />
<link rel="stylesheet" href="extra-large.css" media="(min-width: 120em)" />
<link rel="stylesheet" href="print.css" media="print" />

Браузеры загружают файлы CSS с разным приоритетом:

不符合当前上下文的 CSS 文件将以 _最低_ 优先级进行下载。

Браузер по-прежнему будет загружать все файлы CSS, но только те файлы CSS, которые соответствуют текущему контексту, будут блокировать рендеринг.

Избегайте использования в файлах CSS@import

Следующая задача по уменьшению задержки рендеринга довольно проста:Избегайте использования в файлах CSS@import

если ты понимаешь@importСледует понимать, что его производительность невысока, и его использование заблокирует рендеринг на более длительное время. Это связано с тем, что мы создаем больше (поставленных в очередь) сетевых запросов на критическом пути:

  1. скачать HTML;
  2. Запросить и скачать зависимый CSS;
    • (Однако после загрузки и синтаксического анализа должно быть построено дерево рендеринга;)
  3. CSS зависит от других CSS, продолжайте запрашивать и загружать файлы CSS;
  4. Построить дерево визуализации.

Вот соответствующие случаи:

<link rel="stylesheet" href="all.css" />

Содержимое all.css:

@import url(imported.css);

В конечном итоге водопад запросов браузера отображается следующим образом:

Файлы CSS на критическом пути не загружаются параллельно.

поставив@importsЗапрошенный файл изменен на<link rel="stylesheet" />:

<link rel="stylesheet" href="all.css" />
<link rel="stylesheet" href="imported.css" />

Может улучшить производительность сети:

Файлы CSS на критическом пути загружаются параллельно.

Уведомление, есть особый случай, который стоит обсуждать. Если вы не включите@importЧтобы позволить браузеру загружать файл CSS параллельно, вы можете добавить соответствующий HTML-код в HTML-код.<link rel="stylesheet" src="@import的地址" />. Браузер загружает соответствующие файлы CSS параллельно и не загружает повторно@importссылочные документы.

Используйте экономно в HTML@import

Содержание этого раздела довольно странно. Кажется, есть проблема с реализацией крупных браузеров. Я представил его раньшеСвязанныйизbugs(Примечание переводчика: проще говоря, когда страница существует:<style>@import url(xxx.url);</style>, браузер качает не параллельно, а после кавычек:<style>@import url("xxx.url");</style>, браузер будет скачивать параллельно).

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

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

использовать в HTML@import, в браузерах на основе WebKit и Blink может вызвать ошибку в их сканере предварительной загрузки, а в Firefox и IE/Edge — неэффективно.

Firefox против IE/Edge: добавить HTML@importдо JS и CSS

В Firefox и IE/Edge сканеры предварительной загрузки не загружаются параллельно<script src="">а также<link rel="stylesheet" />назад@importsСсылочные ресурсы.

Это означает следующий HTML:

<script src="app.js"></script>

<style>
  @import url(app.css);
</style>

Появится каскадная диаграмма запроса, подобная этой:

Активы не могут быть загружены параллельно в Firefox из-за недопустимого сканера предварительной загрузки (такая же проблема в IE/Edge).

Из приведенного выше рисунка ясно видно, что до тех пор, пока файл JavaScript не будет загружен,@importНачинается загрузка указанного CSS-файла.

не только<script>тег вызывает эту проблему,<link>Этикетки также будут:

<link rel="stylesheet" href="style.css" />

<style>
  @import url(app.css);
</style>

а также<script>Как и теги, подресурсы нельзя загружать параллельно.

Самое простое решение этой проблемы - поменять местами<script>или<link rel="stylesheet" />этикетка с (содержит@importиз)<style>Расположение этикетки. Однако изменение порядка может повлиять на страницу.

Лучшее решение — вообще не использовать@import, а затем добавьте еще один в HTML-документ<link rel="stylesheet" />Вместо:

<link rel="stylesheet" href="style.css" />
<link rel="stylesheet" href="app.css" />

После модификации браузер ведет себя лучше:

Браузеры загружают ресурсы параллельно, IE/Edge ведут себя одинаково.

Браузеры на основе Blink или WebKit: использование в HTML-документах@import, заключите адрес в кавычки.

Для браузеров на базе Blink или WebKit:когда@importКогда цитируемый URL не заключен в кавычки, ведет себя одинаково с Firefox и IE/Edge (параллельная загрузка невозможна). Это означает, что в сканере предварительной загрузки для двух вышеупомянутых ядер есть ошибка.

Таким образом, нет необходимости корректировать порядок кода, просто добавление кавычек решит проблему. Но я все же рекомендую использовать другой<link rel="stylesheet" />заменять@import.

Добавлен код без кавычек:

<link rel="stylesheet" href="style.css" />

<style>
  @import url(app.css);
</style>

Схема водопада:

Как видите, отсутствие кавычек нарушает предварительную загрузку Chrome (Opera и Safari ведут себя также).

Код после добавления кавычек:

<link rel="stylesheet" href="style.css" />

<style>
  @import url("app.css");
</style>

Сканеры предварительной загрузки Chrome, Opera и Safari работают нормально после добавления кавычек,

Это определенно ошибка в WebKit и ядре Blink, добавление кавычек или нет не должно влиять на предварительную загрузку сканера.

благодарныйYoavПомогите мне отследить эту проблему.

Эта ошибка теперь находится на рассмотрении в Chromium.списоксередина.

Не размещайте динамически вставляемый код JavaScript в<link rel="stylesheet" />Позже

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

<script>
  var script = document.createElement('script');
  script.src = "analytics.js";
  document.getElementsByTagName('head')[0].appendChild(script);
</script>

Малоизвестное, но логичное явление, существующее во всех браузерах, может иметь большое влияние на производительность:

Следующий JS не будет выполняться до тех пор, пока браузер не завершит загрузку файла CSS.

<link rel="stylesheet" href="slow-loading-stylesheet.css" />
<script>
  console.log("I will not run until slow-loading-stylesheet.css is downloaded.");
</script>

Это разумно. Любой синхронизированный код JavaScript в документе HTML не будет выполнен, пока файл CSS не загружен. Рассмотрим следующий сценарий:<script>Код будет обращаться к текущему стилю страницы, чтобы убедиться, что результат правильный, нужно подождать (<script>теги) Все файлы CSS загружаются и анализируются до их получения, в противном случае корректность не может быть гарантирована. Таким образом, перед созданием CSSOM<script>Код не будет выполняться.

В соответствии с этим явлением на время загрузки файлов CSS будут влиять последующие<script>влияют на время выполнения. Следующий пример может лучше проиллюстрировать проблему.

Если мы положим<link rel="stylesheet" />помещать<script>До,<script>Динамически создавать новые<script>код будет выполнен только после того, как файл CSS будет загружен, что означает, что CSS задерживает загрузку и выполнение ресурса:

<link rel="stylesheet" href="app.css" />

<script>
  var script = document.createElement('script');
  script.src = "analytics.js";
  document.getElementsByTagName('head')[0].appendChild(script);
</script>

Как видно из каскадной диаграммы ниже, файлы JavaScript загружаются после сборки CSSOM, полностью теряя преимущества параллельных загрузок:

Хотя сканеры предварительной загрузки предполагают предварительную загрузкуanalytics.js, но правильноanalytics.jsСсылка изначально не находится в HTML-документе, она создается<link>Позади<script>Код создается динамически, перед созданием это просто какая-то строка, вместо предзагрузки распознаваемого сканером ресурса, он скрыт невидимо.

Для более безопасной загрузки скриптов сторонние поставщики услуг часто предоставляют такие фрагменты кода. Однако разработчики часто не доверяют стороннему коду и помещают сниппет в конец страницы, что может привести к нежелательным последствиям. На самом деле, что Google Analytics (в документации) рекомендует для этого:

Скопировав код, вставьте его в качестве первого элемента на отслеживаемой странице.

В общем, мое предложение:

если<script>Код не зависит от CSS, поместите их перед таблицей стилей.

Немного подкорректируйте код:

<script>
  var script = document.createElement('script');
  script.src = "analytics.js";
  document.getElementsByTagName('head')[0].appendChild(script);
</script>

<link rel="stylesheet" href="app.css" />

После обмена местами подресурсы могут загружаться параллельно, и общая производительность страницы увеличивается более чем в два раза. (Примечание переводчика: содержание этого раздела согласовано только наполовину,<head>В коде действительно рекомендуется ставить<script>, затем поставьте<link>, соответствующий контент будет позже, но сторонний код размещен в<head>Первый элемент в зависимости от назначения связанного кода. Если не надо, так же можно скачать и выполнить в конце страницы или при простое)

Поместите код JavaScript, который не требует запроса CSSOM, перед файлом CSS и поместите код JavaScript, который необходимо запрашивать, после файла CSS.

Этот совет гораздо полезнее, чем вы думаете.

Вставка нового<script>Код должен быть помещен в<link>Раньше можно было это обобщить на другие CSS и JavaScript? Чтобы понять эту проблему, сначала сделайте следующие предположения:

Предполагать:

  • Конструкция CSSOM заблокирует выполнение синхронного JS за CSS;
  • Синхронный JS блокирует построение DOM...

Итак, если JS не зависит от CSSOM, что из следующего будет быстрее?

  • сценарий до стиля после;
  • стиль до сценария после?

ответ:

Если файл JS не зависит от CSS, вы должны поместить код JS перед таблицей стилей.Поскольку зависимости нет, то и нет причин блокировать выполнение кода JavaScript.

(Хотя выполнение кода JavaScript прекращает синтаксический анализ DOM, сканер предварительной загрузки загружает CSS заранее)

Если у вас есть какой-то JavaScript, который зависит от CSS, но не зависит от другого, лучше всего разделить JavaScript на две части, по одной с каждой стороны от CSS:

<!-- 这部分 JavaScript 代码下载完后会立即执行 -->
<script src="i-need-to-block-dom-but-DONT-need-to-query-cssom.js"></script>

<link rel="stylesheet" href="app.css" />

<!-- 这部分 JavaScript 代码在 CSSOM 构建完成后才会执行 -->
<script src="i-need-to-block-dom-but-DO-need-to-query-cssom.js"></script>

Благодаря этой организации наши страницы оптимально загружают и выполняют соответствующий код. На скриншоте ниже розовым цветом обозначено выполнение JS, но все они «тонкие», надеюсь, вам это ясно видно. (Первый столбец (такой же внизу)) Первая строка - это таймлайн всей страницы. Обратите внимание на розовую часть этой строки, это означает, что выполняется JS. Вторая строка — это временная шкала первого JS-файла, вы можете видеть, что он сразу загружается и выполняется. Третья строка — это временная шкала CSS, поэтому JS не выполняется. Последняя строка — это временная шкала второго JS-файла, хорошо видно, что он не выполняется, пока не завершится загрузка CSS.

Уведомление, вам следует протестировать эту организацию кода в соответствии с реальной ситуацией на странице, в зависимости от размера файлов CSS и JavaScript и времени, необходимого для выполнения файлов JavaScript, результаты могут быть разными. Не забывайте тестировать больше! (Примечание переводчика: исходя из практического опыта,<head>Организация кода в основном может следовать этому пути, то есть JS предшествует CSS, потому что<head>JS-код в нем в принципе не зависит от CSS, и единственный контрпример — это то, что JS-код очень большой или выполняется долго. )

Буду<link rel="stylesheet" />помещать<body>середина.

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

В HTTP/1.1 мы привыкли вводить все css в один файл, например app.css:

<html>
<head>

  <link rel="stylesheet" href="app.css" />

</head>
<body>

  <header class="site-header">

    <nav class="site-nav">...</nav>

  </header>

  <main class="content">

    <section class="content-primary">

      <h1>...</h1>

      <div class="date-picker">...</div>

    </section>

    <aside class="content-secondary">

      <div class="ads">...</div>

    </aside>

  </main>

  <footer class="site-footer">
  </footer>

</body>

Однако производительность рендеринга снижается по трем причинам:

  1. Каждая страница использует только часть стилей в app.css:Пользователи загружают избыточный CSS.
  2. Сложно разработать стратегию кэширования:Например, если на странице используется средство выбора даты, которое изменяет цвет фона, после повторного создания файла app.css старый кеш app.css станет недействительным.
  3. Отрисовка всего app.css заблокирована до тех пор, пока CSSOM не будет проанализирован и создан:Хотя текущая страница может использовать только 17 % кода CSS, (браузеру) все равно приходится ждать, пока остальные 83 % кода будут загружены и проанализированы, прежде чем он сможет начать рендеринг.

Используя HTTP/2, можно решить первый и второй пункты:

<html>
<head>

  <link rel="stylesheet" href="core.css" />
  <link rel="stylesheet" href="site-header.css" />
  <link rel="stylesheet" href="site-nav.css" />
  <link rel="stylesheet" href="content.css" />
  <link rel="stylesheet" href="content-primary.css" />
  <link rel="stylesheet" href="date-picker.css" />
  <link rel="stylesheet" href="content-secondary.css" />
  <link rel="stylesheet" href="ads.css" />
  <link rel="stylesheet" href="site-footer.css" />

</head>
<body>

  <header class="site-header">

    <nav class="site-nav">...</nav>

  </header>

  <main class="content">

    <section class="content-primary">

      <h1>...</h1>

      <div class="date-picker">...</div>

    </section>

    <aside class="content-secondary">

      <div class="ads">...</div>

    </aside>

  </main>

  <footer class="site-footer">
  </footer>

</body>

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

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

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

Однако это явление было устранено в Chrome (v69), а Firefox и IE/Edge также были оптимизированы.<link rel="stylesheet" />Блокируется только последующее содержимое, а не отображение всей страницы. Это означает, что мы можем организовать наш код следующим образом:

<html>
<head>

  <link rel="stylesheet" href="core.css" />

</head>
<body>

  <link rel="stylesheet" href="site-header.css" />
  <header class="site-header">

    <link rel="stylesheet" href="site-nav.css" />
    <nav class="site-nav">...</nav>

  </header>

  <link rel="stylesheet" href="content.css" />
  <main class="content">

    <link rel="stylesheet" href="content-primary.css" />
    <section class="content-primary">

      <h1>...</h1>

      <link rel="stylesheet" href="date-picker.css" />
      <div class="date-picker">...</div>

    </section>

    <link rel="stylesheet" href="content-secondary.css" />
    <aside class="content-secondary">

      <link rel="stylesheet" href="ads.css" />
      <div class="ads">...</div>

    </aside>

  </main>

  <link rel="stylesheet" href="site-footer.css" />
  <footer class="site-footer">
  </footer>

</body>

В результате мы можем отображать страницу постепенно, и когда предыдущий CSS будет доступен, страница будет отображать соответствующий контент (вместо ожидания загрузки и анализа всего CSS).

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

Подробнее об этой функции, рекомендованное чтениеэта статья.

Суммировать

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

  • Ленивая загрузка некритического CSS:
    • Сначала загрузите ключевой CSS и лениво загрузите другой CSS;
    • Или разделите файлы CSS на основе типа мультимедиа.
  • избегать использования@import:
    • следует избегать в документах HTML;
    • Его следует избегать в файлах CSS;
    • И будьте осторожны со странным поведением предварительно загруженных сканеров.
  • Сосредоточьтесь на порядке CSS и JavaScript:
    • JavaScript после файлов CSS будет выполняться только после сборки CSSOM;
    • Если ваш JavaScript не зависит от CSS;
      • поместите его перед CSS;
    • Если JavaScript зависит от CSS:
      • Поместите его после CSS.
  • Загружать только DOM-зависимый CSS:
    • Это повысит скорость начального рендеринга и позволит постепенно отображать страницу.

Уведомление

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

благодарный

благодарныйYoav,Andyа такжеRyanКомментарии и корректура этой статьи за последние несколько дней.