Веб-разработка

Как браузер отображает веб-страницу? - DOM, CSSOM и рендеринг

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

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

Во-первых, нам нужно понять, что такое DOM. Когда браузер отправляет серверу запрос на выборку HTML-документа, сервер возвращает HTML-страницу в формате двоичного потока, который в основном представляет собой текстовый файл с заголовком ответа Content-Type, установленным на значение text/html; charset=UTF-8.

Здесь text/html - это MIME-тип, который сообщает браузеру, что это документ HTML, а charset=UTF-8 сообщает браузеру, что он закодирован в символе UTF-8. кодирование. Используя эту информацию, браузер может преобразовать двоичный формат в читаемый текстовый файл. Это показано ниже на скриншоте.

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

В приведенном выше документе наша веб-страница зависит от style.css для предоставления стилей HTML-элементам и от main.js для выполнения некоторых операций JavaScript. С некоторыми изящными стилями CSS наша веб-страница будет выглядеть так.

Но все еще остается вопрос: как браузер отображает эту красивую веб-страницу из простого HTML-файла, который не содержит ничего, кроме текста? Для этого нам нужно понять, что такое DOM, CSSOM и дерево рендеринга?

Объектная модель документа (DOM)

Когда браузер читает HTML-код, всякий раз, когда он встречает такой HTML-элемент, как html, body, div и т. Д., Он создает объект JavaScript под названием Узел. В конце концов, все элементы HTML будут преобразованы в объекты JavaScript.

Поскольку каждый элемент HTML имеет разные свойства, объект Node будет создан из разных классов (функций-конструкторов). Например, объект Node для элемента div создается из HTMLDivElement, который наследует класс Node. Для нашего более раннего HTML-документа мы можем визуализировать эти узлы, используя простой тест, как показано ниже.

Браузер имеет встроенные классы, такие как HTMLDivElement, HTMLScriptElement, Node и т. Д.

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

Дерево DOM для нашего более раннего HTML-документа выглядит так, как показано выше. Дерево DOM начинается с самого верхнего элемента, который является html элементом, и разветвляется в соответствии с вхождением и вложенностью элементов HTML в документ. При обнаружении элемента HTML он создает объект узла DOM (Node) из своего соответствующего класса (функция-конструктор).

💡 Узел DOM не всегда должен быть элементом HTML. Когда браузер создает дерево DOM, он также сохраняет такие вещи, как комментарии, атрибуты, текст как отдельные узлы в дереве. Но для простоты мы просто рассмотрим узлы DOM для HTML-элементов AKA DOM element. Здесь - список всех типов узлов DOM.

Вы можете визуализировать дерево DOM в консоли Google Chrome DevTools, как показано ниже. Это покажет вам иерархию элементов DOM (высокоуровневое представление дерева DOM) со свойствами каждого элемента DOM.

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

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

Объектная модель CSS (CSSOM)
Когда мы разрабатываем веб-сайт, мы стремимся сделать его как можно более красивым. И мы делаем это, предоставляя некоторые стили HTML-элементам. На странице HTML мы предоставляем стили для элементов HTML с помощью CSS, что означает каскадные таблицы стилей. Используя селекторы CSS, мы можем настроить таргетинг на элементы DOM и установить значение для свойства стиля, например color или font-size.

Существуют различные методы применения стилей к элементам HTML, например использование внешнего файла CSS, встроенного CSS с использованием тега <style>, встроенного метода с использованием атрибута style в элементах HTML или использования JavaScript. Но, в конце концов, браузеру приходится делать тяжелую работу по применению стилей CSS к элементам DOM.

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

После построения модели DOM браузер считывает CSS из всех источников (внешних, встроенных, встроенных, пользовательских агентов и т. Д.) и создает CSSOM. CSSOM означает объектную модель CSS, которая представляет собой древовидную структуру, аналогичную DOM.

Каждый узел в этом дереве содержит информацию о стиле CSS, которая будет применяться к элементам DOM, на которые он нацелен (заданный селектором). CSSOM, однако, не содержит элементов DOM, которые нельзя распечатать на экране, например <meta>, <script>, <title> и т. Д.

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

Даже если свойство CSS (например display) для конкретного элемента HTML не определено ни разработчиком, ни браузером, его значение устанавливается на значение по умолчанию для это свойство, как указано в стандарте W3C CSS. При выборе значения по умолчанию для свойства CSS используются некоторые правила наследования, если свойство соответствует требованиям наследования, как указано в Документации W3C.

Например, color и font-size среди прочего наследуют значение родительского элемента, если эти свойства отсутствуют для элемента HTML. Итак, вы можете представить, что эти свойства есть у элемента HTML и всех его дочерних элементов, наследующих его. Это называется каскадированием стилей, поэтому CSS является аббревиатурой от каскадных таблиц стилей. Именно по этой причине браузер создает CSSOM, древовидную структуру для вычисления стилей на основе правил каскадирования CSS.

💡 Вы можете увидеть вычисленный стиль элемента HTML с помощью консоли Chrome DevTools на панели Элементы. Выберите любой элемент HTML на левой панели и нажмите вкладку вычислено на правой панели.

Мы можем визуализировать дерево CSSOM для нашего предыдущего примера, используя приведенную ниже диаграмму. Для простоты мы проигнорируем стили пользовательского агента и сосредоточимся на стилях CSS, упомянутых ранее.

Как видно из приведенной выше диаграммы, наше дерево CSSOM не содержит элементов, которые не выводятся на экран, например <link>, <title>, <script> и т. Д. Значения свойств CSS в красных цветах каскадируются сверху вниз, а значения свойств в gray переопределяют унаследованные значения.

Дерево рендеринга

Render-Tree также представляет собой древовидную структуру, созданную путем объединения деревьев DOM и CSSOM. Браузер должен рассчитать макет каждого видимого элемента и нарисовать их на экране, поскольку он использует это дерево рендеринга. Следовательно, если Render-Tree не построено, ничего не будет напечатано на экране, поэтому нам нужны деревья DOM и CSSOM.

Поскольку Render-Tree представляет собой низкоуровневое представление того, что в конечном итоге будет напечатано на экране, оно не будет содержать узлов, которые не удерживают какую-либо область в матрице пикселей. Например, display:none; элементы имеют размеры 0px X 0px, поэтому они не будут присутствовать в Render-Tree.

Как видно из приведенной выше диаграммы, Render-Tree объединяет DOM и CSSOM для создания древовидной структуры, содержащей только элементы, которые будут напечатаны на экране.

Поскольку в CSSOM элемент p, расположенный внутри div, имеет display:none; стиль, он и его дочерние элементы не будут присутствовать в дереве рендеринга, так как он не занимает места на экране. Однако, если у вас есть элементы с visibility:hidden или opacity:0, они будут занимать место на экране, следовательно, они будут присутствовать в дереве рендеринга.

В отличие от DOM API, который предоставляет доступ к элементам DOM в дереве DOM, созданном браузером, CSSOM скрыт от пользователя. Но поскольку браузер объединяет DOM и CSSOM для формирования дерева рендеринга, браузер предоставляет доступ к узлу CSSOM элемента DOM, предоставляя высокоуровневый API для самого элемента DOM. Это позволяет разработчику получать доступ к свойствам CSS узла CSSOM или изменять их.

💡 Поскольку управление стилями элемента с помощью JavaScript выходит за рамки этой статьи, вот ссылка на замечательную Статью о хитростях CSS, которая охватывает широкий спектр CSSOM. API. У нас также есть новый API Типизированный CSS-объект в JavaScript, который является более точным способом манипулирования стилями элемента.

Последовательность рендеринга

Теперь, когда у нас есть хорошее представление о том, что такое DOM, CSSOM и Render-Tree, давайте разберемся, как при просмотре отображается типичная веб-страница с их использованием. Минимальное понимание этого процесса имеет решающее значение для любых веб-разработчиков, поскольку это поможет нам разработать наш веб-сайт для максимального удобства работы пользователей (UX) и производительности.

Когда веб-страница загружается, браузер сначала считывает текст HTML и строит из него дерево DOM. Затем он обрабатывает CSS независимо от того, является ли он встроенным, встроенным или внешним, и строит из него дерево CSSOM.

После того, как эти деревья построены, он строит из них Render-Tree. Как только дерево рендеринга построено, браузер запускает печать отдельных элементов на экране.

Макет операции

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

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

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

Операция покраски

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

Создание слоев помогает браузеру эффективно выполнять операции рисования на протяжении всего жизненного цикла веб-страницы, например, при прокрутке или изменении размера окна браузера. Наличие слоев также помогает браузеру правильно рисовать элементы в порядке наложения (по оси z), как это было задумано разработчиком.

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

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

Аналогия со слоями в Photoshop может быть применена и к тому, как браузер отображает веб-страницу. Вы можете визуализировать различные слои на веб-странице с помощью Chrome DevTools. Откройте DevTools и в разделе Дополнительные инструменты выберите Слои. Вы также можете визуализировать границы слоя на панели Визуализация.

💡 Растеризация обычно выполняется в ЦП, что делает ее медленной и дорогостоящей, но теперь у нас есть новые методы, позволяющие сделать это в графическом процессоре для повышения производительности. В этой статье подробно рассматривается тема рисования, ее необходимо прочитать. Чтобы понять концепцию слоев более подробно, необходимо прочитать статью this.

Операция компоновки

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

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

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

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

💡 Марико Косака написала красивую статью об этом процессе с классными иллюстрациями и более широкими объяснениями каждой концепции. Настоятельно рекомендуется.

Браузерные движки

Работа по созданию дерева DOM, дерева CSSOM и логики обработки обработки выполняется с помощью программного обеспечения, называемого движок браузера (, также известный как механизм рендеринга или Layout Engine), который находится внутри браузера. Этот движок браузера содержит все необходимые элементы и логику для рендеринга веб-страницы от HTML-кода до фактических пикселей на экране.

Если вы слышали, что люди говорят о WebKit, они говорят о движке браузера. WebKit используется браузером Apple Safari и был механизмом рендеринга по умолчанию для браузера Google Chrome. На данный момент проект Chromium использует Blink в качестве механизма рендеринга по умолчанию. Вот список различных браузерных движков, используемых некоторыми ведущими веб-браузерами.

Процесс отображения в браузерах

Все мы знаем, что язык JavaScript стандартизирован с помощью стандарта ECMAScript, фактически, поскольку JavaScript является зарегистрированным товарным знаком, мы сейчас называем его просто ECMAScript. Следовательно, каждый поставщик движка JavaScript, такой как V8, Chakra, Spider Monkey и т. Д., Должен подчиняться правилам этот стандарт.

Наличие стандарта дает нам согласованный опыт работы с JavaScript во всех средах выполнения JavaScript, таких как браузеры, Node, Deno и т. Д. Это отлично подходит для последовательной и безупречной разработки приложений JavaScript (и веб) для нескольких платформ.

Однако это не относится к тому, как браузер отображает данные. HTML, CSS или JavaScript, эти языки стандартизированы какой-либо организацией. Однако то, как браузер управляет ими вместе, чтобы отображать объекты на экране, не стандартизирован. Механизм браузера Google Chrome может работать иначе, чем движок браузера Safari.

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

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



Парсинг и внешние ресурсы

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

Большинство браузеров предоставляют DOMParser веб-API для построения дерева DOM из HTML-кода. Экземпляр класса DOMParser представляет парсер DOM, и с помощью метода прототипа parseFromString мы можем анализировать необработанный текст HTML (код) в дерево DOM (как показано ниже).

Когда браузер запрашивает веб-страницу и сервер отвечает некоторым текстом HTML (с заголовком Content-Type, установленным на text/html), браузер может начать анализ HTML, как только появится несколько символов. или доступны строки всего документа. Следовательно, браузер может строить дерево DOM постепенно, по одному узлу за раз. Браузер анализирует HTML снизу вверх, а не в середине, поскольку HTML представляет собой вложенную древовидную структуру.

В приведенном выше примере мы получили доступ к файлу incremental.html с нашего сервера Node и установили скорость сети только на 10 кбит / с (на панели Сеть). Поскольку браузеру потребуется много времени для загрузки (загрузки) этого файла (поскольку он содержит 1000 h1 элементов), браузер создает дерево DOM из первых нескольких байтов и печатает их на экране (по мере загрузки оставшегося содержимого HTML-файла в фоновом режиме).

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

💡 Используйте значок на вкладке "Производительность", чтобы зафиксировать профиль производительности.

FP - это аббревиатура от First Paint, что означает время, когда браузер начал печатать что-либо на экране (может быть таким же простым, как первый пиксель цвета фона тела ).

FCP - это аббревиатура от First Contentful Paint, которая означает время, когда браузер визуализировал первый пиксель контента, например текст или изображение . LCP - это аббревиатура от Самая большая содержательная краска, которая означает время, когда браузер визуализировал большие фрагменты текста или изображения.

💡 Возможно, вы слышали о FMP (первая значимая краска), который также является метрикой, аналогичной LCP, но был удален из Chrome в пользу LCP.

L обозначает событие onload, которое запускается браузером на объекте window. Точно так же DCL обозначает событие DOMContentLoaded, которое генерируется на объекте document, но оно увеличивается до window, поэтому вы также можете прослушивать его на window. Эти события немного сложны для понимания, поэтому мы обсудим их немного позже.

Когда браузер обнаруживает внешний ресурс, например файл скрипта (JavaScript) через элемент <script src="url"></script>, файл таблицы стилей (CSS) через тег <link rel="stylesheet" href="url"/>, файл изображения через элемент <img src="url" /> или любой другой внешний ресурс, браузер начнет загрузку этого файла в фоновом режиме (вне основного потока выполнения JavaScript).

Самое важное, о чем следует помнить, - это то, что анализ DOM обычно происходит в основном потоке. Поэтому, если основной поток выполнения JavaScript занят, синтаксический анализ DOM не будет продолжаться, пока поток не станет свободным. Вы спросите, почему это так важно? Потому что script элементы блокируют парсер. Запросы всех внешних файлов, таких как изображение, таблица стилей, pdf, видео и т. Д., Не блокируют построение модели DOM ( синтаксический анализ), кроме запросов файлов сценария (.js).

Скрипты блокировки парсера

Сценарий блокировки синтаксического анализатора - это файл / код сценария (JavaScript), который останавливает синтаксический анализ HTML. Когда браузер обнаруживает элемент script, если это встроенный сценарий, он сначала выполнит этот сценарий, а затем продолжит синтаксический анализ HTML для построения дерева DOM. Итак, все встроенные скрипты блокируют парсер, конец обсуждения.

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

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

Браузер предоставляет DOM API среде выполнения JavaScript, что означает, что мы можем получать доступ к элементам DOM и управлять ими из JavaScript. Так работают динамические веб-фреймворки, такие как React и Angular. Но если браузер хочет запускать парсинг DOM и выполнение скрипта параллельно, тогда между потоком парсера DOM и основным потоком могут быть условия гонки, поэтому парсинг DOM должно происходить в основном потоке.

Однако в большинстве случаев прекращение синтаксического анализа DOM во время загрузки файла сценария в фоновом режиме совершенно не требуется. Следовательно, HTML5 дает нам атрибут async для тега script. Когда парсер DOM встречает внешний элемент script с атрибутом async, он не останавливает процесс синтаксического анализа, пока файл сценария загружается в фоновом режиме. Но как только файл будет загружен, синтаксический анализ остановится, и сценарий (код) будет выполнен.

У нас также есть волшебный атрибут defer для элемента script, который работает аналогично атрибуту async, но в отличие от атрибута async скрипт не выполняется даже после полной загрузки файла. Все defer сценарии выполняются после того, как синтаксический анализатор проанализирует весь HTML, что означает, что дерево DOM полностью построено. В отличие от async скриптов, все defer скрипты выполняются в том порядке, в котором они появляются в документе HTML (или дереве DOM).

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

В приведенном выше примере файл parser-blocking.html содержит сценарий блокировки синтаксического анализатора после 30 элементов, поэтому браузер сначала отображает 30 элементов, останавливает синтаксический анализ DOM и начинает загрузку файла сценария. Второй файл сценария не блокирует синтаксический анализ, поскольку он имеет атрибут defer, поэтому он будет выполняться после того, как дерево DOM будет полностью построено.

Если мы посмотрим на панель Производительность, FP и FCP появятся как можно скорее (скрыты за меткой Время), поскольку браузер начинает строить дерево DOM, как только становится доступен некоторый HTML-контент, следовательно, он может отображать некоторые пиксели на экране.

LCP происходит через 5 секунд, потому что сценарий блокировки синтаксического анализатора заблокировал синтаксический анализ DOM на 5 секунд (время загрузки), и только 30 текстовых элементов были отображены на экране, когда анализатор DOM был заблокирован, что недостаточно, чтобы называться самой крупной содержательной краской (в соответствии со стандартами Google Chrome). Но как только скрипт был загружен и выполнен, синтаксический анализ DOM возобновился, и на экране был отображен большой контент, что привело к срабатыванию события LCP.

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

Некоторые браузеры могут включать стратегию спекулятивного анализа, при которой анализ HTML (, но не построение дерева DOM) выгружается в отдельный поток, чтобы браузер мог читать такие элементы, как link (CSS), script, img и т. Д. И с нетерпением загрузите эти ресурсы.

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

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

💡 Каждый браузер имеет собственное мнение, поэтому не гарантируется, когда или если произойдет спекулятивный анализ. Однако вы можете попросить браузер заранее загрузить некоторые ресурсы, используя элемент <link rel="preload">.

CSS с блокировкой рендеринга

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

Механизмы браузера внутри вашего браузера создают дерево DOM из содержимого HTML, полученного с сервера в виде текстового документа. Точно так же он создает дерево CSSOM из содержимого таблицы стилей, например из внешнего файла CSS или встроенного (, а также встроенного) CSS в HTML.

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

Как мы узнали, создание дерева DOM является инкрементным, что означает, что когда браузер читает HTML, он будет добавлять элементы DOM в дерево DOM. Но это не относится к дереву CSSOM. В отличие от дерева DOM, построение дерева CSSOM не инкрементально и должно происходить определенным образом.

Когда браузер находит блок <style>, он анализирует весь встроенный CSS и обновляет дерево CSSOM с новыми правилами CSS (style). После этого он продолжит анализ HTML в обычном режиме. То же самое и со встроенным стилем.

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

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

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

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

CSS - это ресурс, который блокирует рендеринг. Когда браузер делает запрос на получение внешней таблицы стилей, построение дерева визуализации останавливается. Поэтому Критический путь рендеринга (CRP) также застревает, и на экране ничего не отображается, как показано ниже. Однако построение дерева DOM все еще продолжается, пока таблица стилей загружается в фоновом режиме.

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

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

Представим сценарий, в котором браузер начал анализировать HTML и обнаружил внешний файл таблицы стилей. Он начнет загрузку файла в фоновом режиме, заблокирует CRP и продолжит синтаксический анализ DOM. Но затем он встречает тег script. Таким образом, он начнет загрузку внешнего файла сценария и заблокирует синтаксический анализ DOM. Теперь браузер простаивает, ожидая полной загрузки таблицы стилей и файла сценария.

Но на этот раз внешний файл сценария был загружен полностью, а таблица стилей все еще загружается в фоновом режиме. Должен ли браузер выполнять файл сценария? Есть ли в этом вред?

Как мы знаем, CSSOM предоставляет высокоуровневый JavaScript API для взаимодействия со стилями элементов DOM. Например, вы можете прочитать или обновить цвет фона элемента DOM, используя свойство elem.style.backgroundColor. Объект style, связанный с элементом elem, предоставляет CSSOM API, и есть много других API, которые делают то же самое (прочтите эту статью о css-трюках).

Поскольку таблица стилей загружается в фоновом режиме, JavaScript все еще может выполняться, поскольку основной поток не блокируется загрузкой таблицы стилей. Если наша программа JavaScript обращается к свойствам CSS элемента DOM (через CSSOM API), мы получим правильное значение (в соответствии с текущим состоянием CSSOM).

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

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

В приведенном выше примере script-blocking.html содержит тег link (для внешней таблицы стилей), за которым следует тег script (для внешнего JavaScript). Здесь скрипт загружается очень быстро, без каких-либо задержек, но таблица стилей загружается за 6 секунд. Следовательно, даже несмотря на то, что скрипт загружен полностью, как мы видим на панели Сеть, он не был запущен браузером сразу. Только после загрузки таблицы стилей мы видим сообщение Hello World, записанное сценарием.

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

Событие DOMContentLoaded документа

Событие DOMContentLoaded (DCL) отмечает момент времени, когда браузер построил полное дерево DOM из всего доступного HTML. Но есть много факторов, которые могут измениться при запуске события DCL.

document.addEventListener( 'DOMContentLoaded', function(e) {
    console.log( 'DOM is fully parsed!' );
} );

Если наш HTML-код не содержит скриптов, синтаксический анализ DOM не будет заблокирован, и DCL сработает так же быстро, как браузер сможет проанализировать весь HTML-код. Если у нас есть сценарии блокировки синтаксического анализатора, то DCL придется ждать, пока все сценарии блокировки синтаксического анализатора не будут загружены и выполнены.

Все становится немного сложнее, когда в картину добавляются таблицы стилей. Даже если у вас нет внешних скриптов, DCL будет ждать загрузки всех таблиц стилей. Поскольку DCL отмечает момент времени, когда все дерево DOM готово, но доступ к DOM будет небезопасным (для информации о стиле), если CSSOM также не будет полностью построен. Следовательно, большинство браузеров ждут, пока все внешние таблицы стилей не будут загружены и проанализированы.

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

DCL - один из показателей производительности веб-сайта. Мы должны оптимизировать DCL, чтобы он был как можно меньше (время, в которое это происходит). Одна из лучших практик - по возможности использовать теги defer и async для элемента script, чтобы браузер мог выполнять другие действия, пока скрипты загружаются в фоновом режиме. Во-вторых, мы должны оптимизировать таблицы стилей блокировки сценариев и блокировки отображения.

Событие окна load

Как мы знаем, JavaScript может блокировать создание дерева DOM, но это не относится к внешним таблицам стилей и файлам, таким как изображения, видео и т. Д.

Событие DOMContentLoaded отмечает момент времени, когда дерево DOM полностью построено и к нему можно безопасно получить доступ, событие window.onload отмечает момент времени, когда загружаются внешние таблицы стилей и файлы и наше веб-приложение (завершено ) закончил скачивание.

window.addEventListener( 'load', function(e) {
  console.log( 'Page is fully loaded!' );
} )

В приведенном выше примере файл rendering.html имеет внешнюю таблицу стилей в head, загрузка которой занимает около 5 секунд. Поскольку он находится в разделе head, FP и FCP появляются через 5 секунд, поскольку таблица стилей блокирует отображение любого содержимого под ней (поскольку блокирует CRP).

После этого у нас есть элемент img, который загружает изображение, загрузка которого занимает около 10 секунд. Таким образом, браузер продолжит загрузку этого файла в фоновом режиме и продолжит синтаксический анализ и рендеринг DOM (поскольку внешний ресурс изображения не блокирует ни анализатор, ни рендеринг).

Затем у нас есть три внешних файла JavaScript, загрузка которых занимает 3, 6 и 9 секунд соответственно, и, что наиболее важно, это не async. Это означает, что общее время загрузки должно быть близко к 18 секундам, поскольку последующий скрипт не начнет загрузку раньше, чем будет выполнен предыдущий. Однако, глядя на событие DCL, наш браузер, похоже, использовал спекулятивную стратегию для быстрой загрузки файлов сценария, поэтому общее время загрузки близко к 9 секундам.

Поскольку последний загружаемый файл, который может повлиять на DCL, является последним файлом сценария со временем загрузки 9 секунд (поскольку таблица стилей уже была загружена через 5 секунд), DCL событие происходит примерно через 9,1 секунды.

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