Главная » Статьи » Как работает CSS: парсинг и отрисовка CSS в критическом пути рендера

Как работает CSS: парсинг и отрисовка CSS в критическом пути рендера

Как работает CSS: парсинг и отрисовка CSS в критическом пути рендера

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

Неважно, что вы думаете: CSS – это «необходимое зло» или мощный, но непонятный инструмент. С CSS приходится работать всем, кто создает веб-приложения. Глубокое понимание CSS может стать тем барьером, который отделяет красивое, отполированное веб-приложение от чего-то среднего.

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

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

Причина, почему нас интересует путь, который CSS проходит, чтобы превратиться в красивые пиксели, сводится к двум словам.

Время загрузки

Если ваш сайт загружается целую вечность, есть вероятность, что пользователи не будут ждать, даже если на сайте есть ценный контент. Несколько исследований показали, что до 50% пользователей закрывают страницу после 3 секунд ожидания.

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

Что такое критический путь рендера?

Когда мы говорим, что пользователи хотят быструю загрузку, нам необходимо провести линию между критическими и некритическими ресурсами. Может быть, вы лениво загружаете некоторые изображения или настроили разбиение роутов (спасибо, Webpack!), чтобы не посылать весь JS за раз. Такие ресурсы, загружающиеся после первичного рендера страницы, считаются не критичными. То есть они не задерживают первичный рендер страницы. Ресурсы, которые задерживают первый рендер страницы, считаются критическими.

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

Строим DOM (Document Object Model) из полученного HTML

Если натыкаемся на CSS (вставленные или по ссылке), начинаем строить SSOM (CSS Object Model  — позже узнаем, что это)

Если натыкаемся на блок JS (не помеченный как async) во время построения DOM, ждем построения CSSOM, останавливаем создание DOM и парсим/выполняем код. Причина такого поведения кроется в том, что выполнение JS может менять DOM и получать доступ/менять CSSOM.

В рамках статьи разберем второй шаг – как CSS влияет на критический путь рендера. Очень легко увлечься tree shake, route split и lazy load для JS и забыть про CSS. Неоптимизированный пакет CSS может сильно увеличить время загрузки.

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

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

DOM – это древоподобная структура данных, содержащая все узлы HTML страницы. Каждый узел хранит информацию об HTML элементе (атрибуты, id и классы). Если у узла есть дочерние HTML элементы, он также будет вести на их дочерние узлы. Например, по HTML ниже мы построим следующий DOM. Обратите внимание, как отступы HTML и структура DOM похожи.

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

Создание объектной модели CSS

Когда браузер сталкивается с CSS (вставленным или внешним), ему необходимо распарсить текст во что-то, что моно использовать для стилизации макетов и отрисовок. Структура данных, в которую браузер преобразует CSS называется CSSOM – объектная модель CSS.

Как выглядит CSSOM? По CSS ниже браузер построит следующую CSSOM.

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

Перевод CSS в CSSOM считается этапом, блокирующим рендер, как и построение DOM из HTML. Если бы стили рендерились в пиксели постепенно без ожидания CSSOM, мы получили бы мигание нестилизованного контента (страшно!) на время парсинга CSSOM. После применения стилей все бы съехало. Это точно не хороший UX.

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

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

Сперва браузер удаляет все невидимые элементы. Среди таких элементов head, script и meta, а также HTML элементы с атрибутом hidden. Эти элементы используются другими частями приложения и не будут рендериться на странице. Поэтому браузер может безопасно приступить к рендеру, зная, что все элементы в дереве рендера – это видимые HTML элементы.

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

Но есть одно CSS правило-исключение. display: none; в CSS полностью удаляет элемент из дерева рендера. Это касается только видимых элементов в дереве рендера. Другие способы сокрытия элемента типа opacity: 0; не удаляют элемент из дерева рендера, а рендерят его, не показывая.

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

Макет и отрисовка

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

Макет – то, где браузер решает, где будет расположен элемент, и сколько пространства он будет занимать. Браузер принимает правила, влияющие на margin, padding, width и position. При вычислении макета браузер должен пробежаться сверху дерева рендера вниз, так как положение всех элементов, их ширина и высота вычисляются относительно положения родительских узлов.

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

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

Отрисовка происходит сразу после этапа макетирования, и мы наконец видим что-то на странице! Если представить, что время до первого пикселя это конец гонки, то это финишная черта. Браузер проходит по всем CSS блокам и заполняет их немакетными правилами. Если вы используете несколько слоев, браузер проверит, чтобы все было на своих слоях.

Важно помнить, что одни CSS свойства могут сильнее влиять на вес страницы, чем другие (например, radial-gradient намного сложнее для отрисовки, чем просто color). Если во время отрисовки вы испытываете «подергивания», уменьшите количество «дорогих» CSS правил. Это сильно повысит воспринимаемую производительность приложения.

Зачем думать о CSS в критическом пути рендера?

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

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

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

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

Прежде чем начать полную перестройку CSS, оцените время загрузки через профайлер (это касается всего, что связано с веб-производительностью). Если вы используете Chrome, откройте DevTools и перейдите на вкладку Performance. Здесь сразу видно, сколько времени тратится на создание CSSOM, макетирование и отрисовку. Для этого взгляните на события Recalculate Styles, Layout и Paint. После этого уже можно исправлять узкие места и проводить оптимизацию.

LogRocket, DVR для веб-приложений

LogRocket – front end инструмент для логирования, позволяющий воспроизводить проблемы, как если бы они были в вашем браузере. Чтобы не гадать, почему произошла ошибка и не просить у пользователей скриншоты и дампы с логами, LogRocket позволяет воспроизводить сессию, чтобы быстро понять, что пошло не так. Инструмент замечательно работает с любым приложением, независимо от фреймворка, и имеет плагины для логирования дополнительного контекста из Redux, Vuex и @ngrx/store.

Помимо логирования действий и состояний Redux LogRocket записывает логи консоли, JS ошибки, трассировки стека, сетевые запросы/ответы с заголовками + тело запроса/ответа, метаданные браузера и кастомные логи. Он также позволяет DOM записывать HTML и CSS на странице, воссоздавая идеальное видео для самых сложных одностраничных приложений. Попробуйте бесплатно.

Автор: Benjamin Johnson

Источник: https://blog.logrocket.com/

Редакция: Команда webformyself.