Во время того, как вы разрабатываете свое веб-приложение, совершенно нормально, что закулисная работа браузера остается для вас мифической тенью за вашим кодом, и процессы выполняются без вашего непосредственного в них участия. Но по мере роста ваших компетенций во фронтенд-разработке, а также при необходимости оптимизировать высоконагруженные приложения, где даже микрооптимизация может принести большую бизнес- и пользовательскую ценность, понимание процессов браузерной работы становится важным шагом на пути к вашему умению выявлять и устранять уязвимые места. Давайте погрузимся в браузерную работу глубоко, но на доступном языке обсудим происходящие там процессы и найдем возможности их оптимизации.
После ввода пользователем имени сайта в адресную строку и после выполнения браузером ряда процессов навигации, то есть последовательности запросов, цель которых — конвертация имени сайта в IP-адрес сервера, браузер начинает загружать HTML-файл. Рекомендую рассказ Данилы Фетисова про разрешение доменного имени - DNS resolution
Браузер начинает парсинг HTML, не дожидаясь его полной загрузки. Другими словами, начинается получение HTML/CSS-кода и преобразование его в то, с чем браузер может работать. Получив первый чанк информации, браузер анализирует значение doctype, чтобы определить, какие алгоритмы парсинга будут использоваться парсером. Парсинг выполняется движком браузера.
Обработка HTML схожа с общей практикой языковой компиляции, хотя и имеет свои значительные отличия. Тут также присутствует процесс токенизации (разбиения кода на лексемы), а после токенизации для HTML идет инкрементальный процесс построения DOM (описание содержимого HTML-файла в виде дерева тегов). Парсер обрабатывает строку за строкой сверху вниз. В следующий момент времени браузер получает CSS-стили. Начинается парсинг этого файла. Цель — построение CSSOM. Это объект, представляющий стили, связанные с DOM. Он выглядит так же, как DOM, но с соответствующими стилями для каждого узла документа.
Сами параллельные парсинги не блокируют процессы, однако CSS является рендер-блокирующим ресурсом, так как без построения CSSOM браузер не начнет третий этап — компоновку Render tree.
После построения DOM и CSSOM запускается построение Render tree. Это визуальная репрезентация документа. В Render tree скрываются невидимые элементы (например, display: none) и добавляются те, которых нет в DOM, такие как псевдоэлементы ::after
и ::before
. Начиная с корневого элемента DOM, браузер проходит через каждую ноду. Для каждой ноды находится подходящая запись в CSSOM. В итоге браузер создает видимые узлы с содержимым и их вычисленными стилями. Узлы в Render tree называются Renderer (возможно название Render Object и Render Node в зависимости от браузера и спецификации).
На этом этапе браузер инициирует ключевые этапы рендеринга
Первым из них является layout.
Layout
Layout — это рекурсивный процесс. Браузер начинает рекурсивно вызывать функцию layout у каждого Renderer, начиная с корневого Renderer, который соответствует элементу тега <html>
. Компоновка продолжается рекурсивно по всей иерархии Render tree - вычисляется геометрическая информация для каждого узла. Цель процесса лейаута — чтобы каждый Renderer вызовом функции заполнил свое служебное поле ширины и высоты, а также знал X и Y координаты относительно корневого рендерера. Изначально, браузеру известно то, что элемент является дочерним по отношению к своему родителю, но этой информации недостаточно для отображения этого элемента. Браузеру доступна позиция корневого рендерера, она равна (0, 0), а его размеры — это область просмотра viewport — видимая часть окна браузера. Каждый renderer имеет свой layout-метод и вызывает метод layout своих дочерних элементов. Передавая значение координаты вниз, родитель дает возможность дочернему элементу разрешить свое расположение. Когда вызов дошел до самого вложенного элемента, все элементы расположились в соответствии со знанием своей координаты и этот самый вложенный элемент зарегистрировал в своем Renderer то количество пикселей ширины и высоты, которое приписывается ему стилевыми директивами и правилами размещения. Теперь, ближайший родитель элемента/ов, зная размеры своего вложенного элемента, знает и свой размер, и может его зарегистрировать. И так информация заполняется наверх. То есть знание о компоновке детей обязательно для завершения layout на каждом рендерере. В действительности это можно мысленно соотнести с процессами перехода события по DOM-дереву - погружением и всплытием. Вниз по рендерерам - координаты, вверх - размеры
Paint
Браузеру все еще недостаточно иметь DOM, стили, расположение элементов и их размеры для рендеринга. Допустим, вы пытаетесь воспроизвести картину. Вы знаете размер, форму и расположение элементов, но все еще должны решить, в каком порядке их рисовать. Например, для некоторых элементов может быть задан z-index, и в этом случае рисование элементов в том порядке, как они расположены в HTML, приведет к некорректному рендерингу.
На этапе отрисовки браузер создает Paint Records, чтобы определить порядок рисования элементов и другие детали. Rendering Engine браузера не работает напрямую с Render Tree, ему нужны технические команды для отображения изображений. Paint Records создают директивы на уровне машинного кода, которые нужны для отображения пикселей на экране. Некоторые визуальные составляющие требуют больше усилий для применения, чем другие. Например, сложное градиентное фоновое изображение потребует больше времени для составления Paint Records и их выполнения, чем простой сплошной цвет на фоне.
Compositing
Следующая задача — отобразить задуманное на экране. Самый простой способ справиться с этой задачей — растрировать части внутри области просмотра. Если пользователь прокручивает страницу, перемещается растрированный фрейм и заполняются недостающие части, растрируя еще больше.
Однако браузеры уже значительное время работают не так. Современные браузеры запускают более сложный процесс, называемый композицией. Когда разделы документа отрисованы на разных слоях, и один слой находится над другим или перекрывает его, становится необходима композиция. Этот шаг позволяет браузеру гарантировать, что каждый слой отрисован на экране в правильном порядке, а содержимое отображается корректно.
Тут возникает логичный вопрос, откуда появляется множество слоев. В целом необходимость слоистой структуры возникла с появлением аппаратного ускорения Центральный процессор размещён на материнской плате компьютера и занимается выполнением основной части процессов. Графический процессор (GPU) размещен на графической карте компьютера и отвечает за обработку и отрисовку графики. GPU создан специально для выполнения сложных математических и геометрических вычислений, требуемых для отрисовки графики. Следовательно, если переложить часть операций на GPU, можно получить существенное повышение быстродействия и снизить загрузку CPU, особенно на мобильных устройствах. Основной выигрыш происходит потому, что такие элементы в дальнейшем обрабатываются как текстуры видеокарты.
Думайте о текстурах как о растровом изображении, которое перемещается из основной памяти (ОЗУ) в видеопамять и существует как неразрывная единица. Анимация может быть достигнута путем перемещения слоев и композиции нового кадра, а скроллинг становится легковесным, что особенно важно для мобильных устройств. Например, если для свойств transform, opacity, filter
установлены значения, отличные от значений по умолчанию, это приведет к созданию слоя или стекового контекста для элемента, к которому они применены. Их изменение происходит только во время композиции и не вызывает пересчета layout.
FLIP
Еще один интересный метод оптимизации — FLIP (First, Last, Invert, Play). Метод применяется для того, чтобы избежать процессов пересчета layout и провести анимацию только на этапе композиции. Чтобы добиться этого результата используется знание, описанное выше, а именно то, что изменения, связанные с transform, opacity и filter происходят полностью на уровне GPU. Что реализовать метод необходимо:
Взять начальные координаты элемента
Взять конечные координаты, где объект должен оказаться после анимации, разместить объект в этом месте
Создать иллюзию что объект находится в начальной точке - используем transform для перемещения объекта в это фейковое начало
Анимируем перемещение из верхней позиции в желаемую
Получаем то, что элемент с самого начала был установлен в финальное место, а в визуальное начало оттянут с помощью свойства transform
. На протяжении всего процесса layout был выполнен только в самом начале на моменте инициализации страницы. Избегание layout дает значительный выигрыш в перфомансе.
Помимо такого использования нативной оптимизации существуют и другие оптимизации. Например, CSS-свойство will-change форсирует выделение элемента на отдельный слой.
Заключение
Современные браузеры выполняют сложный многоэтапный процесс, чтобы преобразовать HTML, CSS и JavaScript в визуальное представление, которое мы видим на экране. Эти процессы включают в себя парсинг, построение DOM и CSSOM, создание render tree, layout, отрисовку, композицию и оптимизацию с помощью аппаратного ускорения. Также мы на практическом примере рассмотрели, как знание этапов рендеринга позволяет оптимизировать клиентские приложения и ускорять их работу.
На этом все, спасибо что вы здесь.
✌️ Всегда рад предложениям и обратной связи - bronnikovmb@gmail.com
sunnybear
DNS resolving