Вы все еще беспокоитесь об оптимизации производительности рендеринга веб-страниц?

JavaScript браузер HTML CSS
Вы все еще беспокоитесь об оптимизации производительности рендеринга веб-страниц?

блогЕсть еще отличные статьи.

Принцип рендеринга

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

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

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

  1. Обработайте HTML и постройте дерево DOM.
  2. Обработка CSS и построение дерева CSSOM.
  3. Объедините дерево DOM и дерево CSSOM в дерево объектов рендеринга.
  4. Рассчитайте геометрическую информацию узла в соответствии с деревом объектов рендеринга и разместите его.
  5. Чтобы отрисовать страницу, вам нужно сначала построить дерево слоев рендеринга, чтобы отображать страницы в правильном порядке.Генерация этого дерева синхронизирована с построением дерева объектов рендеринга. Затем необходимо построить дерево графических слоев, чтобы избежать ненужного рисования, и использовать аппаратно-ускоренный рендеринг для окончательного отображения страницы на экране.

DOM Tree

DOM (объектная модель документа) — это документ API, используемый для визуализации и взаимодействия с произвольным HTML или XML. DOM — это загруженная в браузер модель документа, представляющая документ в виде дерева узлов, где каждый узел представляет компонент документа.

Следует отметить, что только создание атрибутов документа DOM и отношений с тегами не указывает, что элементы стиля должны быть отображены, что требует работы с CSSOM.

процесс сборки

После получения байтовых данных HTML дерево DOM будет построено с помощью следующего процесса:

  1. Кодировка: необработанные байтовые данные HTML преобразуются в строку с указанной кодировкой файла.
  2. Лексический анализ (токенизация): дословно сканирует входную строку в соответствии справила словообразованияПроцесс идентификации слов и символов и разделения их на слова, которые мы можем понять (научное название Token).
  3. Анализ синтаксиса (парсер): процесс построения дерева DOM путем применения правил грамматики HTML к токенам, сопряжения тегов, установления отношений между узлами и связывания атрибутов.

Лексический анализ и синтаксический анализ выполняют этот процесс каждый раз, когда обрабатывается строка HTML, например, с помощьюdocument.writeметод.

Лексический анализ (токенизация)

Структура HTML не слишком сложна.В большинстве случаев распознаваемые теги будут иметь начальные теги, теги содержимого и конечные теги, соответствующие элементу HTML. Кроме того, есть теги DOCTYPE, Comment, EndOfFile и другие.

Токенизация достигается с помощью конечных автоматов, а модель конечного автоматаW3Cбыл определен в .

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

<a href="www.w3c.org">W3C</a>

  • Начальный тег:<a href="www.w3c.org">
    1. Состояние данных: обнаружено<, войдите в открытое состояние тега
    2. Тег открытое состояние: встречаa, введите состояние состояния имени тега
    3. Состояние имени тега: встречапространство, введите Перед состоянием имени атрибута
    4. Перед состоянием имени атрибута: встречаh, введите состояние Имя атрибута
    5. Состояние имени атрибута: встреча=, введите Состояние перед значением атрибута
    6. До состояния значения атрибута: встреча"В состояние значения атрибута (двойные кавычки)
    7. Состояние значения атрибута (в двойных кавычках): обнаруженоw, сохранить текущее состояние
    8. Состояние значения атрибута (в двойных кавычках): обнаружено", введите состояние После значения атрибута (в кавычках)
    9. Состояние после значения атрибута (в кавычках): встреча>, введите состояние данных и завершите синтаксический анализ
  • Разметка контента:W3C
    1. Состояние данных: обнаруженоW, сохранить текущее состояние, извлечь содержимое
    2. Состояние данных: обнаружено<, войдите в открытое состояние тега и завершите синтаксический анализ
  • конечный тег:</a>
    1. Тег открытое состояние: встреча/, введите открытое состояние конечного тега
    2. Конечное метка Открытое состояние: METa, введите состояние имени тега
    3. Состояние имени тега: встреча>, введите состояние данных и завершите синтаксический анализ

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

Синтаксический анализ (парсер)

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

Я кратко представлю процесс, конкретный процесс реализации можно найти вTree constructionПроверить.

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

использованная литература

  1. Как работают браузеры: за кулисами современных веб-браузеров — комбинация парсера и лексера
  2. Процесс рендеринга в браузере и оптимизация производительности — построение дерева DOM и дерева CSSOM
  3. За браузером (1) - Лексический анализ языка HTML
  4. За браузером (2) — Грамматический анализ языка HTML
  5. Компилятор HTML в 50 строках кода
  6. Основы синтаксического анализа AST: как написать простую библиотеку для синтаксического анализа html
  7. Лексический анализ HTML в WebKit
  8. Разбор HTML-документа и построение дерева DOM
  9. Посмотрите, как браузер строит дерево DOM из исходного кода Chrome.
  10. Построение объектной модели — объектная модель документа (DOM)

CSSOM Tree

нагрузка

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

блокировать

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

Чтобы проиллюстрировать более подробно на примере:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <script>
    var startDate = new Date();
  </script>
  <link href="https://cdn.bootcss.com/bootstrap/4.0.0-alpha.6/css/bootstrap.css" rel="stylesheet">
  <script>
    console.log("link after script", document.querySelector("h2"));
    console.log("经过 " + (new Date() - startDate) + " ms");
  </script>
  <title>性能</title>
</head>
<body>
  <h1>标题</h1>
  <h2>标题2</h2>
</body>
</html>

Во-первых, вам нужно установить регулирование сети на панели «Сеть» консоли Chrome, чтобы снизить скорость сети для лучшей отладки.

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

Зачем нам нужно блокировать выполнение JavaScript?

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

Разобрать

Шаги парсинга CSS очень похожи на парсинг HTML.

лексический анализ

CSS будет разбит на следующую разметку:

Лучше ли использовать шестнадцатеричное представление для значений цвета CSS, чем функциональное представление?

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

Разбор

Каждый файл CSS или встроенный стиль будет соответствовать объекту CSSStyleSheet (authorStyleSheet), который состоит из набора правил (правил); каждое правило будет содержать селекторы (селекторы) и несколько деклараций (объявлений), а декларация состоит из свойства ( свойство ) и Value (значение). Кроме того, таблица стилей браузера по умолчанию (defaultStyleSheet) и таблица стилей пользователя (UserStyleSheet) также будут иметь соответствующие объекты CSSStyleSheet, поскольку они являются отдельными файлами CSS. Что касается встроенных стилей, они напрямую анализируются в коллекции объявлений при построении дерева DOM.

Разница между линейными стилями и авторскими

Все авторские таблицы стилей смонтированы вdocumentузел, мы можем сделать это в браузере черезdocument.styleSheetsПолучите эту коллекцию. Встроенные стили можно передавать непосредственно черезstyleвид собственности.

Давайте поймем разницу между встроенными стилями и authorStyleSheet на примере:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <style>
    body .div1 {
      line-height: 1em;
    }
  </style>
  <link rel="stylesheet" href="./style.css">
  <style>
    .div1 {
      background-color: #f0f;
      height: 20px;
    }
  </style>
  <title>Document</title>
</head>
<body>
  <div class="div1" style="background-color: #f00;font-size: 20px;">test</div>
</body>
</html>

Вы можете видеть, что всего имеется три объекта CSSStyleSheet, каждый объект CSSStyleSheet имеет CSSStyleDeclaration в правилах, а встроенный стиль напрямую получен CSSStyleDeclaration.

Комбинированные свойства это нужно?

При обнаружении слияния атрибутов при разборе объявления одно объявление будет преобразовано в соответствующие несколько объявлений, например:

.box {
  margin: 20px;
}

margin: 20pxОн будет преобразован в четыре объявления; это показывает, что, хотя CSS поддерживает слияние атрибутов, в конечном итоге оно будет разделено; поэтому роль слияния атрибутов должна заключаться в уменьшении объема кода CSS.

рассчитать

Зачем нужно вычислять?

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

Готов к работе

Для облегчения вычислений после создания объекта CSSStylesheet объект CSSStylesheet сохраняется в соответствующей хэш-карте. Например, все правильные типы селекторов — это правила id, которые будут храниться в карте правил ID. чтобы быстрее соответствовать всем правилам текущего элемента, затем проверьте, соответствует ли ваш следующий селектор текущему элементу.

idRules
classRules
tagRules
...
*
Селектор попал

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

Правила сопоставления справа налево

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

Зачем нужно соответствие справа налево?

Сначала подумайте о процессе прямого сопоставления, мы используемdiv p .yellowНапример, сначала найти всеdivузел, а затем посмотрите вниз, чтобы увидеть, являются ли потомкиpузел, если да, посмотрите вниз, чтобы увидеть, есть лиclass="yellow", соответствует, если он существует, но что, если это не так? Просто тратьте запрос, если на странице тысячиdivЕсли только один узел соответствует Правилу, это вызовет много недопустимых запросов, а если большинство недопустимых запросов будет найдено в конце, потеря производительности будет слишком велика.

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

задать стиль

Установите стиль заказа, наследуемый от родительского узла, а затем используйте стиль пользовательского агента и, наконец, используйте стили разработчика (authorStyleSheet).

АвторПриоритет таблицы стилей

При попадании в результирующий набор будет рассчитан приоритет этого Правила, давайте взглянем на определение веса приоритета ядром blink:

switch (m_match) {
  case Id: 
    return 0x010000;
  case PseudoClass:
    return 0x000100;
  case Class:
  case PseudoElement:
  case AttributeExact:
  case AttributeSet:
  case AttributeList:
  case AttributeHyphen:
  case AttributeContain:
  case AttributeBegin:
  case AttributeEnd:
    return 0x000100;
  case Tag:
    return 0x000001;
  case Unknown:
    return 0;
}
return 0;

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

/*
 * 65793 = 65536 + 1 + 256
 */
#container p .text {
  font-size: 16px;
}

/*
 * 2 = 1 + 1
 */
div p {
  font-size: 14px;
}

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

Приоритет встроенного стиля

После обработки правила authorStyleSheet будет установлен встроенный стиль, встроенный стиль обработан и сохранен в папке узла.styleатрибут включен.

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

!importantприоритет

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

Правила написания CSS

Набор результатов, наконец, сгенерирует объект ComputedStyle, доступ к которому можно получить с помощьюwindow.getComputedStyleметод для просмотра всех объявлений.

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

Настроить стиль

На этом шаге настраиваются соответствующие объявления, например, объявлениеposition: absolute;, текущий узелdisplayбудет установлен наblock.

использованная литература

  1. Посмотрите, как браузер вычисляет CSS из исходного кода Chrome.
  2. Изучите принципы синтаксического анализа CSS
  3. Webkit Kernel Exploration [2] — Реализация Webkit CSS
  4. Анализ CSS-движка Webkit
  5. Будет ли загрузка css вызывать блокировку?
  6. Оказывается, CSS и JS блокируют синтаксический анализ и рендеринг DOM следующим образом.
  7. Внешний CSS задерживает синтаксический анализ DOM и DOMContentLoaded
  8. CSS/JS блокирует синтаксический анализ и рендеринг DOM
  9. Построение объектной модели — объектная модель CSS (CSSOM)
  10. Блокирующий рендеринг CSS

Render Object Tree

После построения дерева DOM и дерева CSSOM будет сгенерировано дерево объектов рендеринга (узел документа является особым случаем).

Создать объект рендеринга

При создании узла документа объект рендеринга будет одновременно создан как корень дерева. Render Object — это объект визуализации, описывающий стиль положения узла, размер и т. д.

каждый не-display: none | contentsКаждый узел создаст Render Object, и процесс будет примерно следующим: сгенерируйте ComputedStyle (описанный в разделе расчета дерева CSSOM), затем сравните старый и новый ComputedStyle (старый ComputedStyle по умолчанию пуст в начале); если нет , создать новый Render Object и связать с текущим обрабатываемым узлом, а затем установить родственные отношения родитель-потомок, чтобы сформировать полное Render Object Tree.

макет (переформатирование)

После того, как Render Object добавлен в дерево, ему нужно пересчитать позицию и размер, ComputedStyle уже содержит эту информацию, зачем ее пересчитывать? потому что нравитсяmargin: 0 auto;Такое объявление нельзя использовать напрямую, и его необходимо преобразовать в фактический размер, прежде чем узел сможет быть отрисован механизмом рисования; это одна из причин, по которой дерево DOM и дерево CSSOM необходимо объединить в дерево объектов рендеринга.

Макет начинается рекурсивно с корневого объекта рендеринга, и у каждого объекта рендеринга есть метод для самого себя. Почему вам нужно рекурсивно (то есть сначала вычислить дочерний узел, а затем вернуться к родительскому узлу) для вычисления положения и размера? Поскольку некоторая информация о макете должна быть сначала рассчитана дочерними узлами, а затем положение и размер родительского узла могут быть рассчитаны на основе информации о макете дочерних узлов; например, высота родительского узла должна поддерживаться дочерние узлы. Что, если ширина дочернего узла составляет 50% высоты родительского узла? Это требует вычисления собственной информации о макете перед вычислением дочернего узла, а затем передачи ее дочернему узлу.После того, как дочерний узел будет рассчитан на основе этой информации, он сообщит родительскому узлу, нужно ли его пересчитывать.

Числовой тип

Все относительные измерения (rem,em, процент...) должны быть преобразованы в абсолютные пиксели на экране. еслиemилиrem, вам нужно вычислить пиксель на основе родительского узла или корневого узла. Если это процент, его нужно умножить на максимальную ширину или высоту родительского узла. еслиauto, нужно использовать(父节点的宽或高 - 当前节点的宽或高) / 2Вычислите стоимость обеих сторон.

коробочная модель

Как мы все знаем, каждый элемент документа представлен в виде прямоугольного блока (box model), через который можно наглядно описать структуру макета Render Object, в комментариях к исходному коду blink это наглядно описанокоробочная модель, В отличие от привычного, полоса прокрутки также включена в блочную модель, но не все браузеры могут изменять размер полосы прокрутки.

// ***** THE BOX MODEL *****
// The CSS box model is based on a series of nested boxes:
// http://www.w3.org/TR/CSS21/box.html
//                              top
//       |----------------------------------------------------|
//       |                                                    |
//       |                   margin-top                       |
//       |                                                    |
//       |     |-----------------------------------------|    |
//       |     |                                         |    |
//       |     |             border-top                  |    |
//       |     |                                         |    |
//       |     |    |--------------------------|----|    |    |
//       |     |    |                          |    |    |    |
//       |     |    |       padding-top        |####|    |    |
//       |     |    |                          |####|    |    |
//       |     |    |    |----------------|    |####|    |    |
//       |     |    |    |                |    |    |    |    |
//  left | ML  | BL | PL |  content box   | PR | SW | BR | MR |
//       |     |    |    |                |    |    |    |    |
//       |     |    |    |----------------|    |    |    |    |
//       |     |    |                          |    |    |    |
//       |     |    |      padding-bottom      |    |    |    |
//       |     |    |--------------------------|----|    |    |
//       |     |    |                      ####|    |    |    |
//       |     |    |     scrollbar height ####| SC |    |    |
//       |     |    |                      ####|    |    |    |
//       |     |    |-------------------------------|    |    |
//       |     |                                         |    |
//       |     |           border-bottom                 |    |
//       |     |                                         |    |
//       |     |-----------------------------------------|    |
//       |                                                    |
//       |                 margin-bottom                      |
//       |                                                    |
//       |----------------------------------------------------|
//
// BL = border-left
// BR = border-right
// ML = margin-left
// MR = margin-right
// PL = padding-left
// PR = padding-right
// SC = scroll corner (contains UI for resizing (see the 'resize' property)
// SW = scrollbar width
box-sizing

box-sizing: content-box | border-box,content-boxСледуя стандартной блочной модели W3C,border-boxСоответствует модели коробки IE.

Разница между ними заключаетсяcontent-boxсодержит только область содержимого, аborder-boxвключается до границы. Объясните на примере:

// width
// content-box: 40
// border-box: 40 + (2 * 2) + (1 * 2)
div {
  width: 40px;
  height: 40px;
  padding: 2px;
  border: 1px solid #ccc;
}

использованная литература

  1. Посмотрите, как макет макета браузера из исходного кода Chrome
  2. Анализ процесса создания дерева объектов рендеринга на веб-странице Chromium
  3. Как работают браузеры: за кулисами современных веб-браузеров — деревья рендеринга и деревья DOM
  4. Расскажите о моем понимании блочной модели
  5. Визуализировать построение дерева, компоновку и рисунок

Render Layer Tree

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

Создать слой визуализации

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

Причины для создания Render Layer следующие:

  • NormalLayer
    • Атрибут позиции относительный, фиксированный, липкий, абсолютный.
    • Прозрачный (непрозрачность меньше 1), фильтр, маска, режим наложения (режим микс-наложения не является нормальным)
    • клип-путь
    • 2D- или 3D-преобразование (преобразование не имеет значения)
    • скрыть обратную сторону (обратная сторона-видимость: скрытая)
    • Отражение (box-reflection)
    • количество столбцов (не автоматически) или ширина столбца Z (не автоматически)
    • Применение анимации к непрозрачности, преобразованию, фильтрации
  • OverflowClipLayer
    • Вырезать переполнение содержимого (переполнение: скрыто)

Кроме того, объект рендеринга, соответствующий следующим элементам DOM, также создаст отдельный слой рендеринга:

  • Document
  • HTML
  • Canvas
  • Video

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

использованная литература

  1. Оптимизация производительности беспроводной сети: композит — от LayoutObjects до PaintLayers
  2. Анализ процесса создания дерева слоев Chromium Web Render
  3. Рендеринг WEBKIT должен знать эти четыре дерева

Graphics Layer Tree

программный рендеринг

Программный рендеринг — это самый ранний метод рендеринга, принятый браузерами. Таким образом, рендеринг отрисовывает слой рендеринга сзади наперед (рекурсивно); в процессе рисования слоя рендеринга его объекты рендеринга постоянно отправляют запросы на отрисовку в общий графический контекст, чтобы отрисовать себя в общий контекст.битовая картасередина.

аппаратный рендеринг

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

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

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

Каждый графический слой будет иметь соответствующий графический контекст. Графический контекст отвечает за вывод растрового изображения текущего слоя рендеринга.Растровое изображение хранится в системной памяти и загружается в графический процессор в виде текстуры (которую можно понимать как растровое изображение в графическом процессоре).И наконец, графический процессор объединяет несколько растровые изображения, а затем рисует их на экране. Поскольку графический слой будет иметь отдельное растровое изображение, аппаратный рендеринг не перерисовывает связанный с ним слой рендеринга, как программный рендеринг при обновлении веб-страницы в целом; вместо этого он перерисовывает обновленный графический слой.

Причина повышения

Причины, по которым уровень рендеринга становится составным слоем, можно примерно обобщить следующим образом.Оптимизация производительности беспроводной сети: Композит — от PaintLayers к GraphicsLayers.

  • Элемент iframe имеет составной слой.
  • Элемент видео и его панель управления.
  • Используйте элемент холста WebGL.
  • Плагины с аппаратным ускорением, такие как flash.
  • Свойство CSS для трехмерного или перспективного преобразования.
  • обратная видимость скрыта.
  • Применяйте анимацию или переход к непрозрачности, преобразованию, флитеру и образе образуют (он должен быть активной анимацией или переходом. Когда анимация или эффект перехода не запускаются или заканчиваются, продвинутый компонентный слой вернется к обычному слою).
  • will-change устанавливается на непрозрачность, преобразование, верх, левый, нижний, правый (где верхний, левый и т. д. необходимо установить явные свойства позиционирования, такие как относительное и т. д.).
  • Имеет потомка составного слоя и сам имеет некоторые свойства.
  • У элемента есть родственный элемент с более низким z-индексом, который является составным слоем.
Зачем нужен составной слой?
  1. Избегайте ненужных перекрасок. Например, на веб-странице есть два слоя a и b.Если элементы слоя a изменяются, слой b не меняется, тогда вам нужно только перерисовать слой a, а затем выполнить композитинг со слоем b, чтобы получить всю веб-страницу.
  2. Воспользуйтесь преимуществами аппаратного ускорения для эффективной реализации определенных функций пользовательского интерфейса. Такие эффекты, как прокрутка, 3D-преобразование, прозрачность или фильтрация, могут быть эффективно реализованы с помощью графического процессора (аппаратный рендеринг).
сжатие слоев

Из-за перекрытия может быть сгенерировано большое количество составных слоев, что приведет к трате большого количества ресурсов и серьезно повлияет на производительность.Эта проблема называется взрывом слоя. Браузер решает эту проблему с помощью Layer Squashing.Когда несколько слоев рендеринга перекрываются с составным слоем, эти слои рендеринга будут сжаты в один и тот же составной слой. Давайте посмотрим пример:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <style>
    div {
      position: absolute;
      width: 100px;
      height: 100px;
    }
    .div1 {
      z-index: 1;
      top: 10px;
      left: 10px;
      will-change: transform;
      background-color: #f00;
    }
    .div2 {
      z-index: 2;
      top: 80px;
      left: 80px;
      background-color: #f0f;
    }
    .div3 {
      z-index: 2;
      top: 100px;
      left: 100px;
      background-color: #ff0;
    }
  </style>
  <title>Document</title>
</head>
<body>
  <div class="div1"></div>
  <div class="div2"></div>
  <div class="div3"></div>
</body>
</html>

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

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

использованная литература

  1. Оптимизация производительности беспроводной сети: Композит - от -PaintLayers- до -GraphicsLayers
  2. Основы рендеринга Webkit и аппаратное ускорение
  3. Анализ процесса создания дерева графических слоев веб-страницы Chromium
  4. Композитинг с аппаратным ускорением в Chrome
  5. Процесс рендеринга в браузере Подробный анализ
  6. Основа процесса рендеринга WebKit и многоуровневое ускорение

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

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

пиксельный конвейер

JavaScript. В общем, мы будем использовать JavaScript для достижения некоторых визуальных изменений. Например, используйте функцию анимации jQuery для анимации, сортировки набора данных или добавления элементов DOM на страницу. Конечно, помимо JavaScript, есть и другие распространенные способы достижения визуальных изменений, такие как анимация CSS, переходы и API веб-анимации.

Расчет стиля. Этот процесс представляет собой процесс выяснения того, какие элементы применяют какие правила CSS на основе соответствующих селекторов (таких как .headline или .nav > .nav__item ). Зная правила из этого, правила применяются и рассчитывается окончательный стиль каждого элемента.

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

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

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

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

  1. JS/CSS > Стиль > Макет > Краска > Композиция

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

  1. JS/CSS > Стиль > Рисование > Композитинг

Если вы измените атрибут Paint Only элемента DOM, такой как фоновое изображение, цвет текста или тень, эти атрибуты не повлияют на макет страницы, поэтому браузер пропустит процесс макета после завершения вычисления стиля и только рисовать и визуализировать процесс слияния слоев.

  1. JS/CSS > Стиль > Композитинг

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

Доступ к свойствам, влияющим на Layout, Paint и Composite, можно получить черезCSS TriggersПроверьте веб-сайт.

Частота обновления

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

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

Частота обновления (Гц) зависит от аппаратного уровня монитора. Частота кадров (FPS) зависит от видеокарты или программных ограничений.

Каждый повторный рендеринг не может превышать 16,66 мс (1 секунда / 60 раз). Но на самом деле у браузера много работы по уборке, поэтому лучше, чтобы вся наша работа выполнялась в течение 10 миллисекунд. Если время превышено, частота обновления упадет, что приведет к дрожанию страницы и зависанию.

Оптимизируйте выполнение JavaScript

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

window.requestAnimationFrame

В отсутствиеrequestAnimationFrameметод, чтобы выполнить анимацию, мы могли бы использоватьsetTimeoutилиsetIntervalДля запуска визуальных изменений, но проблема с этим подходом в том, что время выполнения callback-функции не фиксировано, оно может быть как раз в конце, или оно не будет выполняться напрямую, что часто приводит к потере кадров и зависанию страницы.

В конечном счете, причина вышеуказанной проблемы заключается во времени, то есть браузеру нужно знать, когда реагировать на функцию обратного вызова.setTimeoutилиsetIntervalТаймер используется для запуска функции обратного вызова, и таймер не может гарантировать точное выполнение.Существует множество факторов, влияющих на время его работы.Если в асинхронной очереди нет других задач, будет его очередь выполнять. Кроме того, мы знаем, что оптимальное время для каждого повторного рендеринга составляет около 16,6 мс, если интервал таймера слишком короткий, это вызоветперерисовка, увеличивает накладные расходы; если он слишком длинный, это задержит рендеринг и сделает анимацию неплавной.

requestAnimationFrameметод отличается отsetTimeoutилиsetInterval, который определяется системой, когда выполняется функция обратного вызова, и запрашит браузер выполнить функцию обратного вызова до следующего повторного рендеринга. Независимо от скорости обновления устройства,requestAnimationFrameВременной интервал экрана будет точно соответствовать времени, необходимому для однократного обновления экрана; например, частота обновления устройства составляет 75 Гц, тогда временной интервал в это время составляет 13,3 мс (1 секунда / 75 раз) . Следует отметить, что хотя этот метод может гарантировать, что функция обратного вызова будет отображаться только один раз в каждом кадре, если в этом кадре выполняется слишком много задач, это все равно вызовет задержку; поэтому он может только гарантировать, что повторная визуализация временной интервал самый короткий.Время обновления экрана.

requestAnimationFrameПодробное описание метода см.MDNСоответствующие документы , давайте узнаем, как использовать его на примере веб-анимации.

let offsetTop = 0;
const div = document.querySelector(".div");
const run = () => {
  div.style.transform = `translate3d(0, ${offsetTop += 10}px, 0)`;
  window.requestAnimationFrame(run);
};
run();

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

использованная литература
  1. requestAnimationFrame, известный как артефакт
  2. Что вы знаете о requestAnimationFrame?
  3. Анализ запросаAnimationFrame
  4. Попрощайтесь с таймерами и перейдите к window.requestAnimationFrame()
  5. requestAnimationFrame работает лучше
  6. Расскажите о цикле анимации requestAnimationFrame.

window.requestIdleCallback

requestIdleCallbackМетод будет выполнять функцию обратного вызова только тогда, когда в конце кадра есть время простоя; он подходит для некоторых задач, которые необходимо обрабатывать во время простоя браузера, таких как загрузка статистики, предварительная загрузка данных, рендеринг шаблонов и т. д.

В прошлом, если вам нужно было иметь дело со сложной логикой и не выполнять сегментирование, пользовательский интерфейс, скорее всего, находился в приостановленном состоянии, и любые интерактивные операции были бы недействительны; в этом случае используйтеsetTimeoutЗадачу можно разделить на несколько модулей, и одновременно обрабатывается только один модуль, что может значительно облегчить эту проблему. Но этот метод имеет сильную неопределенность, мы не знаем, свободен ли этот кадр, если он был заполнен большим количеством задач, то обрабатывать модуль в это время нецелесообразно. Так что в этом случае мы также можем использоватьrequestIdleCallbackметод максимально эффективного использования бездействия для обработки сегментированных задач.

Если нет свободного времени,requestIdleCallbackМожете ли вы просто ждать вечно? Конечно нет.В дополнение к функции обратного вызова, его параметры также имеют необязательный объект конфигурации, который можно использоватьtimeoutСвойство устанавливает время тайм-аута; когда это время достигнуто,requestIdleCallbackОбратный вызов немедленно помещается в очередь событий. Давайте посмотрим, как использовать:

// 任务队列
const tasks = [
  () => {
    console.log("第一个任务");
  },
  () => {
    console.log("第二个任务");
  },
  () => {
    console.log("第三个任务");
  },
];

// 设置超时时间
const rIC = () => window.requestIdleCallback(runTask, {timeout: 3000})

function work() {
  tasks.shift()();
}

function runTask(deadline) {
  if (
    (
      deadline.timeRemaining() > 0 ||
      deadline.didTimeout
    ) &&
    tasks.length > 0
  ) {
    work();
  }

  if (tasks.length > 0) {
    rIC();
  }
}

rIC();

Подробное описание параметров callback-функции можно посмотретьMDNдокументация.

изменить DOM

не должно бытьrequestIdleCallbackФункция обратного вызова метода изменяет DOM. Посмотрим, в конце кадра срабатывает callback-функция, ее положение в кадре:

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

Если вы действительно хотите изменить DOM, лучше всего:requestIdleCallbackФрагмент документа создается в обратном вызове следующего кадра, а затем в следующем кадре.requestAnimationFrameОбратные вызовы вносят реальные изменения в DOM.

Fiber

React 16 представляет новый согласователь, Fiber Reconciler. Отличается от оригинального Stack Reconciler тем, что весь процесс рендеринга не завершается непрерывно и без перерыва, а сегментируется и сегментируется для обработки задач, что требует использованияrequestIdleCallbackа такжеrequestAnimationFrameметод достижения.requestIdleCallbackответственность за малоприоритетные задачи,requestAnimationFrameОтвечает за приоритетные задачи, связанные с анимацией.

использованная литература
  1. requestIdleCallback — планирование фоновых задач
  2. requestIdleCallback, который вы должны знать
  3. использовать requestIdleCallback
  4. Первый взгляд на React Fiber — примирение

Web Worker

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

Web Worker — это спецификация, определенная в HTML5, которая позволяет сценариям JavaScript выполняться в фоновых потоках, отличных от основного потока. Это создает для JavaScriptМногопоточностьВ среде основного потока мы можем создать рабочий поток и назначить ему какие-то задачи. Рабочий поток и основной поток выполняются одновременно, они не мешают друг другу. Подождите, пока рабочий поток завершит задачу, и отправьте результат в основной поток.

Web Workers — это скорее механизм обратного вызова, чем создание многопоточной среды. В конце концов, рабочий поток может использоваться только для вычислений и не может выполнять такие операции, как изменение DOM, он не может совместно использовать память, нетсинхронизация потоковКонцепция чего-либо.

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

Web Workers API

Рабочий поток создаетсяnewкомандный вызовWorker()Создается конструктором; параметрами конструктора являются: файл скрипта, содержащий код для выполнения задачи, импортированный файл скриптаURIДолжна соблюдаться политика того же происхождения.

Рабочие потоки не находятся в том же глобальном контексте, что и основной поток, поэтому следует помнить о нескольких вещах:

  • Они не могут взаимодействовать напрямую, и данные должны передаваться через механизм сообщений; данные копируются в процессе, а не передаются через экземпляры, созданные Worker. Подробности можно найти вПолучение и отправка данных в воркерах: подробное введение.
  • Невозможно использовать DOM,windowа такжеparentЭти объекты, но могут использовать вещи, которые не связаны с глобальным контекстом основного потока, например.WebScoket,indexedDBа такжеnavigatorЭти объекты, другие объекты, которые можно использовать, можно просмотретьФункции и классы, которые могут использовать Web Workers.
Как использовать

В спецификации Web Worker определены два разных типа потоков; один из них — Dedicated Worker (выделенный поток), глобальный контекст которогоDedicatedWorkerGlobalScopeобъект; другой — Shared Worker (общий поток), глобальный контекст которогоSharedWorkerGlobalScopeобъект. Среди них Dedicated Worker можно использовать только на одной странице, а Shared Worker можно использовать на нескольких страницах.

Позвольте мне кратко представить, как его использовать, можно просмотреть больше APIИспользование веб-воркеров.

выделенная тема

Наиболее важной частью следующего кода является то, как отправлять и получать сообщения между двумя потоками, они оба используютpostMessageметод отправки сообщения, используйтеonmessageСобытия отслеживаются. Разница в том, что в основном потокеonmessageсобытия иpostMessageМетод должен быть смонтирован на экземпляре воркера, в рабочем потоке метод экземпляра самого воркера монтируется на глобальный контекст.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Web Worker 专用线程</title>
</head>
<body>
  <input type="text" name="" id="number1">
  <span>+</span>
  <input type="text" name="" id="number2">
  <button id="button">确定</button>
  <p id="result"></p>

  <script src="./main.js"></script>
</body>
</html>
// main.js

const number1 = document.querySelector("#number1");
const number2 = document.querySelector("#number2");
const button = document.querySelector("#button");
const result = document.querySelector("#result");

// 1. 指定脚本文件,创建 Worker 的实例
const worker = new Worker("./worker.js");

button.addEventListener("click", () => {
  // 2. 点击按钮,把两个数字发送给 Worker 线程
  worker.postMessage([number1.value, number2.value]);
});

// 5. 监听 Worker 线程返回的消息
// 我们知道事件有两种绑定方式,使用 addEventListener 方法和直接挂载到相应的实例
worker.addEventListener("message", e => {
  result.textContent = e.data;
  console.log("执行完毕");
})
// worker.js

// 3. 监听主线程发送过来的消息
onmessage = e => {
  console.log("开始后台任务");
  const result= +e.data[0]+ +e.data[1];
  console.log("计算结束");

  // 4. 返回计算结果到主线程
  postMessage(result);
}
общий поток

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

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

  • Конструкторы, создающие экземпляры, отличаются.
  • Основной поток взаимодействует с общим потоком и должен пройти через точно открытый объект порта; оба должны пройти через объект порта перед доставкой сообщения.onmessageсобытие или явный вызовstartметод открытия соединения порта. В выделенных потоках эта часть выполняется автоматически.
// main.js

const number1 = document.querySelector("#number1");
const number2 = document.querySelector("#number2");
const button = document.querySelector("#button");
const result = document.querySelector("#result");

// 1. 创建共享实例
const worker = new SharedWorker("./worker.js");

// 2. 通过端口对象的 start 方法显式打开端口连接,因为下文没有使用 onmessage 事件
worker.port.start();

button.addEventListener("click", () => {
  // 3. 通过端口对象发送消息
  worker.port.postMessage([number1.value, number2.value]);
});

// 8. 监听共享线程返回的结果
worker.port.addEventListener("message", e => {
  result.textContent = e.data;
  console.log("执行完毕");
});
// worker.js

// 4. 通过 onconnect 事件监听端口连接
onconnect = function (e) {
  // 5. 使用事件对象的 ports 属性,获取端口
  const port = e.ports[0];

  // 6. 通过端口对象的 onmessage 事件监听主线程发送过来的消息,并隐式打开端口连接
  port.onmessage = function (e) {
    console.log("开始后台任务");
    const result= e.data[0] * e.data[1];
    console.log("计算结束");
    console.log(this);

    // 7. 通过端口对象返回结果到主线程
    port.postMessage(result);
  }
}
использованная литература
  1. Оптимизируйте выполнение JavaScript — уменьшите сложность или используйте веб-воркеры
  2. Использование веб-воркеров
  3. Углубленная практика применения веб-воркеров HTML5: многопоточное программирование
  4. JS и многопоточность

Функции защиты от тряски и дросселирования

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

Давайте сначала кратко рассмотрим эти две функции:

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

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

Самая большая разница между этими двумя функциями заключается во времени выполнения.Функция защиты от сотрясения будет выполнять обратный вызов события после того, как событие будет запущено и остановлено на определенный период времени, в то время как функция регулирования будет выполнять обратный вызов события через определенный период времени. когда событие срабатывает. Мы используем таймер, чтобы просто реализовать эти две функции, подробная версия может относиться кUnderscoreа такжеЛодаш - разоблачить,Лодаш - дроссель. Функция дросселирования фактически принадлежит браузеру.requestAnimationFrameПосле метода лучше использовать этот метод для вызова обратного вызова события.

Реализовать функцию защиты от тряски

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

function debounce(func, wait) {
  let timeout = null;
  
  return function run(...args) {
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      func.apply(this, args);
    }, wait);
  }
};
Реализовать функцию дросселирования

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

function throttle(func, wait) {
  let timeout = null;

  return function run(...args) {
    if (!timeout) {
      timeout = setTimeout(() => {
        timeout = null;
        func.apply(this, args);
      }, wait);
    }
  }
}
использованная литература
  1. Защита от встряски и дросселирование JS
  2. Отказаться от обработчика ввода
  3. Underscore
  4. Лодаш - разоблачить
  5. Лодаш - дроссель

Уменьшить сложность стиля

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

Избегайте вложенности селекторов

В разделе «Дерево CSSOM» мы узнали: вложенные селекторы будут сопоставляться справа налево, что является рекурсивным процессом, а рекурсия — это трудоемкая операция. Не говоря уже о некоторых селекторах CSS3, требующих дополнительных вычислений, таких как:

.text:nth-child(2n) .strong {
  /* styles */
}

Чтобы определить, какие узлы применяют этот стиль, браузер должен сначала спросить, какой узел имеет этот стиль."strong" classузел? Его родительский узел оказывается даже"text" classузел? Так много вычислений можно сделать с помощью простогоclassИзбегать:

.text-even-strong {
  /* styles */
}

С таким простым селектором браузеру нужно только один раз сопоставить его. Чтобы точно описать такие аспекты, как структура веб-страницы, возможность повторного использования и совместное использование кода, мы можем использовать БЭМ для помощи в разработке.

БЭМ (блок, элемент, модификатор)

БЭМ - это простоclass, в котором рекомендуется, чтобы все элементы имели один класс, а вложенность внутри классов была хорошо организована:

.nav {}
.nav__item {}

Если узел необходимо отличать от других узлов, можно добавить модификаторы, чтобы облегчить разработку:

.nav__item--active {}

Более подробное описание и использование см.Get BEM.

Используйте менее дорогие стили

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

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <style>
    .simple {
      background-color: #f00;
    }
    .complex {
      box-shadow: 0 4px 4px rgba(0, 0, 0, 0.5);
    }
  </style>
  <title>性能优化</title>
</head>
<body>
  <div class="container"></div>
  <script>
    const div = document.querySelector(".container");
    let str = "";
    for (let i = 0; i < 1000; i++) {
      str += "<div class=\"simple\">background-color: #f00;</div>";
      // str += "<div class=\"complex\">box-shadow: 0, 4px, 4px, rgba(0,0,0,0.5);</div>";
    }
    div.innerHTML = str;
  </script>
</body>
</html>

Видно, что Разметка тени — 31,35 мс, краска — 6,43 мс, Разметка фона — 10,81 мс, краска — 4,30 мс. Разница в макете вполне очевидна.

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

использованная литература

  1. Уменьшите объем и сложность вычислений стилей
  2. Спецификация написания CSS БЭМ

Свести к минимуму перекомпоновку и перерисовку

Прежде всего, давайте разберемся, что такое reflow и redraw.

  • переставлятьОтносится к процессу расчета макета путем перестроения части или всего дерева объектов визуализации путем изменения стиля или настройки структуры DOM. Этот процесс будет запущен как минимум один раз, то есть инициализация страницы.
  • перерисоватьОтносится к перерисовке затронутой части на экране.

Наблюдаемый пиксельный канал найдет RedRaw, не обязательно вызывает перегруппирование, например, изменение цвета фона узла, узел будет перерисован, а перестановка не возникает, потому что информация о макете не изменилась; перерезан.

Следующая ситуация приведет к перестановке или перерисовке:

  • Настройте структуру DOM
  • Изменить стили CSS
  • Пользовательские события, такие как прокрутка страницы, изменение размера окна и т. д.

Стратегия оптимизации браузера

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

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

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

div2.style.height = "100px";
div2.style.width = "100px";

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

  • offsetTop/offsetLeft/offsetWidth/offsetHeight
  • scrollTop/scrollLeft/scrollWidth/scrollHeight
  • clientTop/clientLeft/clientWidth/clientHeight
  • getComputedStyle()

Советы по повышению производительности

  1. Воспользуйтесь стратегиями оптимизации браузера. Одни и те же операции DOM (чтение или запись) должны быть сгруппированы вместе. Не вставляйте операцию записи в середине операции чтения.
  2. Не считайте стили слишком часто. Если стиль получен перекомпоновкой, результат лучше кэшировать. Избегайте перестановки при следующем использовании.
// Bad
const div1 = document.querySelector(".div1");
div1.style.height = div1.clientHeight + 200 + "px";
div1.style.width = div1.clientHeight * 2 + "px";

// Good
const div2 = document.querySelector(".div2");
const div2Height = div1.clientHeight + 200;
div2.style.height = div2Height + "px";
div2.style.width = div2Height * 2 + "px";
  1. Не меняйте стили один за другим. Путем измененияclassNameилиcssTextсвойство для изменения стиля за один раз.
// Bad
const top = 10;
const left = 10;
const div = document.querySelector(".div");
div.style.top = top + "px";
div.style.left = left + "px";

// Good
div.className += "addClass";

// Good
div.style.cssText += "top: 10px; left: 10px";
  1. Используйте автономный DOM. Автономный режим означает отсутствие работы на реальных узлах, что может быть достигнуто следующими способами:
  • Управляйте объектом фрагмента документа и добавляйте этот объект в дерево DOM после завершения.
  • использоватьcloneNodeметод, работайте с клонированным узлом, а затем замените исходный узел клонированным узлом
  • установить узел наdisplay: none;(требуется перестановка), затем выполняет несколько операций над этим узлом и, наконец, восстанавливает отображение (требуется перестановка). Таким образом, используются два перекомпоновки и избегается повторный рендеринг.
  • установить узел наvisibility: hidden;и установить наdisplay: none;похоже, но это свойство оптимизировано только для перерисовки и не влияет на перекомпоновку, потому что оно просто скрыто, но узел все еще находится в потоке документов.
  1. настраиватьposition: absolute | fixed;. Узел будет отделен от документооборота, при этом, поскольку влияние этого узла на другие узлы не учитывается, стоимость реорганизации будет относительно небольшой.
  2. Используйте виртуальный DOM, такой как Vue, React и т. д.
  3. Используйте раскладку flexbox. Производительность макета flexbox намного выше, чем у традиционной модели макета, ниже приведено сравнение 1000divПриложение узлаfloatилиflexСравнение стоимости макета. Можно обнаружить, что для одного и того же количества элементов и одного и того же внешнего видаflexНакладные расходы на макет намного меньше (float 37,92 мс | flex 13,16 мс).

использованная литература

  1. Подробное объяснение управления производительностью веб-страницы
  2. Оптимизация рендеринга: перекомпоновка и перерисовка и аппаратное ускорение
  3. Процесс рендеринга в браузере Подробный анализ
  4. Оптимизация производительности анимации CSS

Оптимизация композита

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

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

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

использоватьtransformа такжеopacityсвойства для анимации

Выше мы сказали, что части Layout и Paint пиксельного конвейера можно пропустить, и выполняется только Composite. Способ добиться этого метода рендеринга очень прост: использовать свойства CSS, которые запускают только Composite; в настоящее время доступны только свойства CSS, удовлетворяющие этому условию.transformа такжеopacity.

использоватьtransformа такжеopacityСледует отметить, что элемент должен быть композитным слоем; в противном случае Paint все равно будет запускаться как обычно (макет зависит от ситуации, обычноtransformсработает). Давайте посмотрим пример:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <style>
    .div {
      width: 100px;
      height: 100px;
      background-color: #f00;
      /* will-change: transform; */
    }
  </style>
  <title>性能优化</title>
</head>

<body>
  <div class="div"></div>
  <script>
    const div = document.querySelector(".div");
    const run = () => {
      div.style.transform = "translate(0, 100px)";
    };
    setTimeout(run, 2000);
  </script>
</body>
</html>

мы будем использоватьtransformДавайте двигаться вниз, не будем начинатьdivУзел повышен до составного слоя, как видно из следующего рисунка: Layout и Paint по-прежнему запущены.

В это время положитьdivУзел повышается до составного слоя, и мы обнаруживаем, что Layout и Paint пропущены, как мы и ожидали.

Уменьшить область рисования

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

Лучший способ перейти на составной слой — использовать CSS.will-changeатрибут, его подробное описание можно посмотретьMDNдокументация.

.element {
  will-change: transform;
}

Для неподдерживаемых браузеров самый простой способ взлома — использовать 3D-деформацию для обновления до композитного слоя.

.element {
  transform: translateZ(0);
}

Согласно приведенному выше примеру, мы пытаемся использоватьwill-changeсвойство, чтобы избежать перерисовки фиксированных областей.

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <style>
    .div {
      width: 100px;
      height: 100px;
      background-color: #f00;
    }
    .header {
      position: fixed;
      z-index: 9999;
      width: 100%;
      height: 50px;
      background-color: #ff0;
      /* will-change: transform; */
    }
  </style>
  <title>性能优化</title>
</head>

<body>
  <header class="header">固定区域</header>
  <div class="div">变动区域</div>
  <script>
    const div = document.querySelector(".div");
    const run = () => {
      div.style.opacity = 0.5;
    };
    setTimeout(run, 2000);
  </script>
</body>
</html>

Во-первых, давайте посмотрим на случай не был оптимизирован, кстати браузер для просмотра деталей процесса рисования.

  1. Откройте интерфейс производительности консоли.
  2. Нажмите «Настройки» (маркер 1), чтобы включить анализатор графика (маркер 2).
  3. Начните запись (отметка 3), и когда вы получите нужную информацию, нажмите «Стоп» (отметка 4), чтобы остановить запись.
  4. Нажмите на Paint (маркер 5) для этого кадра, чтобы увидеть детали покраски.
  5. Переключитесь на вкладку Paint Profiler (маркер 6), чтобы увидеть шаги по рисованию.

Как вы можете видеть на изображениях выше (маркеры 7 и 8), фиксированная область действительно затронута, и запускается перерисовка. Давайте сравнимwill-changeВ случае оптимизации атрибутов обнаружено, что фиксированная область не вызывает перерисовку.

Кроме того, мы также можем проверить, повышена ли фиксированная область (отметка 3) до композитного слоя (отметка 4) через детали компоновки (отметка 2) одного кадра (отметка 1), чтобы избежать ненужного рисования.

Разумное управление составным слоем

Обновление до составного слоя оптимизирует производительность, однако, зная, что создание нового составного слоя требует дополнительной памяти и управления, что очень дорого. Таким образом, на устройствах с ограниченными ресурсами памяти улучшение производительности, обеспечиваемое Composite Layer, может не стоить затрат на создание нескольких Composite Layers. В то же время, поскольку растровое изображение каждого составного слоя необходимо загружать в GPU, необходимо учитывать пропускную способность между CPU и GPU и объем памяти, используемый для обработки текстуры GPU.

Мы прошли 1000divузел, чтобы сравнить использование памяти между обычными слоями и теми, которые повышены до составных слоев. Можно обнаружить, что разница вполне очевидна.

Свести к минимуму усиление

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

* {
  /* or transform: translateZ(0) */
  will-change: transform;
}

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

Глядя на этот пример, мы сначала ставимwill-changeАтрибут записывается в состоянии по умолчанию, затем сравните отрисовку после удаления этого атрибута.

.box {
  width: 100ox;
  height: 100px;
  background-color: #f00;
  will-change: transform;
  transition: transform 0.3s;
}
.box:hover {
  transform: scale(1.5);
}

использоватьwill-changeСоставной слой с продвижением свойства:

Обычный слой:

Мы обнаружили, что единственное отличие состоит в том, что начало и конец анимации вызывают перерисовку; пока анимация выполняется, удалите или используйтеwill-changeНет никакой разницы.

Мы упомянули такую ​​причину, когда строили дерево графических слоев:

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

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

предотвратить взрыв слоя

Мы рассмотрели взрыв слоев в дереве графических слоев, который относится к проблеме большого количества дополнительных составных слоев из-за перекрытия. Сжатие слоев в браузере может решить эту проблему в значительной степени.Однако есть много особых случаев, когда композитный слой не может быть сжат; это может привести к тому, что некоторые составные слои будут не такими, как мы ожидали, сказать, что это все еще будет Появляется большое количество дополнительных составных слоев.

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

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <style>
    .animating {
      width: 300px;
      height: 30px;
      line-height: 30px;
      background-color: #ff0;
      will-change: transform;
      transition: transform 3s;
    }

    .animating:hover {
      transform: translateX(100px);
    }

    ul {
      padding: 0;
      border: 1px solid #000;
    }

    .box {
      position: relative;
      display: block;
      width: auto;
      background-color: #00f;
      color: #fff;
      margin: 5px;
      overflow: hidden;
    }

    .inner {
      position: relative;
      margin: 5px;
    }
  </style>
  <title>性能优化</title>
</head>

<body>
  <div class="animating">动画</div>
  <ul>
    <li class="box">
      <p class="inner">提升成合成层</p>
    </li>
    <li class="box">
      <p class="inner">提升成合成层</p>
    </li>
    <li class="box">
      <p class="inner">提升成合成层</p>
    </li>
    <li class="box">
      <p class="inner">提升成合成层</p>
    </li>
    <li class="box">
      <p class="inner">提升成合成层</p>
    </li>
  </ul>
</body>
</html>

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

Хотя этот пример не выглядит перекрывающимся на поверхности, тем не менее, поскольку он может перекрываться другими элементами при запуске анимации, поэтому.animatingЭлементы будут считаться родственными элементами поверх составного слоя. В это время, потому что.boxэлемент установленoverflow: hidden;заставить себя.animatingЭлементы имеют разные контейнеры отсечения, поэтому слои взрываются.

Решение этой задачи тоже очень простое, пусть.animatingэлементальz-indexВыше, чем у других родственных элементов. Поскольку составной слой находится поверх обычных элементов, нет необходимости повышать уровень обычных элементов и исправлять порядок рендеринга. Вот добавляю кстати, по умолчанию приоритет порядка рендеринга составного слоя выше, чем у обычных элементов, но при установке обычных элементовposition: relative;Впоследствии, из-за контекста стека и позади потока документов, он будет иметь приоритет над составным слоем.

.animating {
  position: relative;
  z-index: 1;
  ...
}

Конечно, если родственные элементы должны быть наложены на составной слой, мы также можем поместитьoverflow: hidden;илиposition: relative;Удалите его, чтобы оптимизировать количество создаваемых составных слоев или вообще не создавать составные слои.

использованная литература

  1. Оптимизация производительности беспроводной сети: Композитный
  2. Придерживайтесь только свойств синтезатора и количества уровней управления
  3. Упрощение сложности рисования и уменьшение области рисования
  4. Оптимизация производительности анимации CSS
  5. Используйте CSS3 will-change для улучшения производительности рендеринга прокрутки страниц, анимации и т. д.
  6. Аппаратное ускорение CSS3 тоже имеет свои недостатки.
  7. Глубокое понимание контекста наложения и порядка наложения в CSS.

Суммировать

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

Оптимизацию нельзя делать вслепую, например, обновление общего слоя до Composite Layer при неправильном использовании приведет к очень серьезному потреблению памяти. Мы должны эффективно использовать консоль отладки браузера Google, чтобы помочь нам более подробно понять ситуацию со всеми аспектами веб-страницы, чтобы целенаправленно оптимизировать веб-страницу.

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