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

JavaScript оптимизация производительности
критический путь рендеринга

Обычно нам просто нужно написать HTML, CSS, JavaScript, чтобы отобразить красивую страницу на экране, но как браузер использует наш код для отображения пикселей на экране?

Браузер преобразует HTML, CSS и JavaScript в реальные пиксели, которые отображаются на экране.Серия шагов, которые браузеры проходят в течение этого периода, называется критическим путем рендеринга (Critical Rendering Path).

关键渲染路径
Рисунок 1-1 Конкретные этапы критического пути рендеринга

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

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

1. Создайте модель документа

Браузеры выполняют четко определенный набор шагов для обработки HTML и построения модели DOM. Макроскопически его можно разделить на несколько этапов. Как показано на рис. 1-2.

构建DOM的具体步骤
Рисунок 1-2 Конкретные шаги для построения DOM

Первый шаг (преобразование): браузер считывает необработанные байты HTML с диска или сети и преобразует их на символы в соответствии с указанной кодировкой файла (например, UTF-8), как показано на рисунке 1-3.

将字节码转换成字符
Рисунок 1-3. Код байтового символа в

Второй шаг (токенизация): преобразовать строку в токен, например: «», «

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

将字符串转换成Token
Рисунок 1-4 Преобразование строки в токен

В это время у вас должны возникнуть вопросы, как поддерживать отношения между узлами?

Фактически, это роль токена для идентификации «начального тега» и «конечного тега». Например, узел между начальным и конечным тегами токена «title» должен быть дочерним узлом «title». Как показано на рис. 1-5.

节点之间的关系
Рисунок 1-5 Связь между узлами

Рисунок 1-5 показывает взаимосвязь между узлами. Например, токен «Hello» расположен между начальным тегом «title» и конечным тегом «title», указывая, что токен «Hello» является дочерним узлом «title». "Жетон. Точно так же токен «title» является дочерним узлом токена «head».

Третий шаг (генерация узловых объектов и построение DOM): Фактически, в процессе построения DOM вместо ожидания преобразования всех токенов и последующего создания узловых объектов он потребляет токены при создании токенов для создания узловых объектов. Другими словами, после создания каждого токена он немедленно использует токен для создания объекта узла.

Токен с идентификатором конечного тега не создает объект узла

Объект узла содержит все свойства этого узла. Например<img src="xxx.png" />Объект узла, окончательно сгенерированный меткой, сохранит адрес изображения и другую информацию.

Затем отношения между узлами идентифицируются и связываются с помощью «начального тега» и «конечного тега». Наконец, когда все токены сгенерированы и использованы, мы получаем полное дерево DOM. Процесс создания DOM из токена показан на рис. 1-6.

构建DOM
Рисунок 1-6 Создание DOM

На рис. 1.6 на каждой пунктирной линии есть небольшое число, обозначающее конкретные этапы построения модели DOM. Видно, что первый сгенерированныйhtmlToken и использовать Token для созданияhtmlОбъект узла. затем сгенерироватьheadТокен и использование токена для созданияheadобъект node и связать его сhtmlв дочерних узлах объекта узла. затем сгенерироватьtitleТокен и использование токена для созданияtitleобъект node и связать его сheadв дочерних узлах объекта узла. последний сгенерированныйbodyТокен и использование токена для созданияbodyобъект node и связать его сhtmlв дочерних узлах . Когда все токены израсходованы, мы получаем полное DOM-дерево.

Конкретная реализация построения DOM очень похожа на принцип компиляции шаблонов Vue.Если вы хотите узнать, как процесс построения DOM реализован в коде, вы можете прочитать мою предыдущую статью наСтатья о принципах компиляции шаблона Vue. Вы также можете рассчитывать на мою новую книгу, которая более подробная и тщательная, чем статья о принципах компиляции шаблонов Vue.

2. Создайте CSSOM

DOM захватывает содержимое страницы, но браузеру также необходимо знать, как отображается страница. Поэтому нужно построить CSSOM (объектная модель CSS — объектная модель CSS).

Процесс построения CSSOM очень похож на процесс построения DOM: когда браузер получает фрагмент CSS, первое, что ему нужно сделать, — это идентифицировать токен, затем построить узел и сгенерировать CSSOM. Как показано на рис. 2-1.

构建CSSOM的具体过程
Рисунок 2-1 Конкретный процесс построения CSSOM

Предположим, браузер получает следующий CSS:

body {font-size: 16px;}
p {color: red;}
p span {display:none;}
span {font-size: 14px;}
img {float: right;}

CSSOM, сгенерированный вышеуказанным CSS после ряда шагов, показан на рис. 2-2.

构建CSSOM的过程
Рисунок 2-2 Процесс построения CSSOM

Из рисунка также видно, чтоbodyДочерние узлы узла наследуютbodyправила стиля (размер шрифта 16px). Это каскадное правило, и поэтому CSS называется CSS (каскадные таблицы стилей).

Здесь я хочу сделать отступление. HTML можно анализировать шаг за шагом. Не нужно ждать, пока будут построены все DOM перед построением CSSOM. Вместо этого при разборе HTML для построения DOM, если встречается CSS, CSSOM будет построен сразу, а можно и одновременно. Но CSS не работает, неполный CSS использовать нельзя, потому что каждое свойство CSS может изменить CSSOM, поэтому возникает такая проблема: предположим, первые несколько байтов CSS устанавливают размер шрифта равным16px, а затем установите размер шрифта на14px, то если вся CSSOM построена не полностью, окончательный CSSOM на самом деле будет неточным. Следовательно, перед переходом к следующему этапу он должен дождаться создания CSSOM.

Следовательно, скорость загрузки CSS и скорость построения CSSOM будут напрямую влиять на скорость рендеринга первого экрана, поэтому CSS по умолчанию считается ресурсом, блокирующим рендеринг.

3. Постройте дерево рендеринга

DOM содержит все содержимое страницы, а CSSOM содержит все стили страницы.Теперь нам нужно скомпоновать DOM и CSSOM в дерево рендеринга.

Предположим, теперь у нас есть этот фрагмент кода:

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Demos</title>
    <style>
        body {font-size: 16px;}
        p {color: red;}
        p span {display:none;}
        span {font-size: 14px;}
        img {float: right;}
    </style>
</head>
<body>
    <p>Hello <span>berwin</span></p>
    <span>Berwin</span>
    <img src="https://p1.ssl.qhimg.com/t0195d63bab739ec084.png" />
</body>
</html>

Этот код в конечном итоге встраивается в дерево рендеринга, как показано на рис. 3-1.

构建渲染树
Рисунок 3-1 Построение дерева рендеринга

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

  1. Корневой узел из дерева DOM начнет обход каждого видимого узла.
    1. Некоторые узлы не видны (например, токены скриптов, мета-токены и т. д.) и игнорируются, поскольку они не будут отражены в отображаемом выводе.
    2. Некоторые узлы скрыты CSS и поэтому также игнорируются в дереве рендеринга. Например: картинка вышеp > spanУзел не появится в дереве рендеринга, потому что узел установленdisplay: noneАтрибуты.
  2. Для каждого видимого узла найдите адаптированные для него правила CSSOM и примените их.

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

渲染树与渲染结果
Рисунок 3-2 Дерево рендеринга и результаты рендеринга

4. Макет

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

Результатом процесса компоновки является «блочная модель», которая точно фиксирует точное положение и размер каждого элемента в окне просмотра, при этом все относительные измерения преобразуются в абсолютные пиксели на экране. Как показано на рис. 4-1.

布局
Рисунок 4-1 Компоновка

5. Рисовать

Сразу после завершения компоновки браузер генерирует события «Paint Setup» и «Paint» для преобразования дерева рендеринга в пиксели на экране. Как показано на рис. 5-1.

绘制
Рисунок 5-1 Чертеж

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

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

Все мы знаем, что загрузка, синтаксический анализ и выполнение JavaScript будет блокировать построение DOM, то есть когда анализатор HTML встречает JavaScript при построении DOM, он приостанавливает построение DOM, передавая управление JavaScript engine и дождитесь JavaScript. После завершения работы движка браузер возобновляет построение DOM с того места, где он остановился.

Поскольку JavaScript может изменять содержимое веб-страниц, он может изменить DOM. Если он не заблокирован, то DOM строится здесь, а DOM модифицируется JavaScript там. Как убедиться, что окончательный DOM правильный ? И в чем разница между DOM, полученным в первую секунду, и DOM, полученным в следующую секунду в JS? Это вызовет ряд проблем, поэтому JS блокирует процесс построения DOM, поэтому элементы, лежащие в основе JS, не могут быть получены в JS, потому что DOM еще не построен.

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

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

Что тут происходит?

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

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

Это вызовет серьезные проблемы с производительностью. Мы предполагаем, что для построения DOM требуется одна секунда, а для построения CSSOM — одна секунда. В обычных условиях для одновременного построения DOM и CSSOM требуется всего одна секунда, а затем переходим к следующий этап. Но если вводится JavaScript, то JavaScript блокирует построение DOM и ждет загрузки и сборки CSSOM через секунду, предполагая, что для выполнения JavaScript требуется0.00000001секунд, то после возобновления построения ДОМа с прерванного места, потребуется еще секунда для завершения построения ДОМа, а для перехода на следующий этап требуется всего 2 секунды. Как показано на рис. 6-1.

JS阻塞构建DOM并等待CSSOM
Рисунок 6-1 JS блокирует построение DOM и ожидает CSSOM

Например, следующий код не загружает JS:

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Test</title>
    <link rel="stylesheet" href="https://static.xx.fbcdn.net/rsrc.php/v3/y6/l/1,cross/9Ia-Y9BtgQu.css">
</head>
<body>
    aa
</body>
</html>

Результат выполнения приведенного выше кода показан на рис. 6-2.

CSS不阻塞DOM
Рисунок 6-2 CSS не блокирует DOM

Событие DOMContentLoaded срабатывает примерно через 116 мс.

Добавьте JavaScript в код:

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Test</title>
    <link rel="stylesheet" href="https://static.xx.fbcdn.net/rsrc.php/v3/y6/l/1,cross/9Ia-Y9BtgQu.css">
</head>
<body>
    aa
    <script>
        console.log(1)
    </script>
</body>
</html>

Событие DOMContentLoaded срабатывает через 1,21 с, как показано на рис. 6-3.

CSS阻塞DOM
Рисунок 6-3 CSS, блокирующий DOM

7. Резюме

Критический путь рендеринга (Critical Rendering Path) относится к серии шагов, которые браузеры проходят для преобразования HTML, CSS и JavaScript в реальные пиксели, отображаемые на экране.

Критический путь рендеринга состоит из пяти шагов. Построить DOM -> Построить CSSOM -> Построить дерево рендеринга -> Макет -> Рисовать.

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

Обычно DOM и CSSOM строятся параллельно, но когда браузер сталкивается сscriptтег, построение DOM будет приостановлено до тех пор, пока скрипт не завершит выполнение. Однако, поскольку JavaScript может модифицировать CSSOM, необходимо дождаться создания CSSOM перед выполнением JS.

Адрес моего блога:GitHub.com/Не Эрвин/блог…