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

Если вы пропустили предыдущие главы, вы можете найти их здесь:

До сих пор в наших предыдущих публикациях в блоге серии «Как работает JavaScript» мы уделяли внимание JavaScript как языку, его функциям, способам его выполнения в браузере, способам его оптимизации и т. Д.

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

Итак, давайте посмотрим, каковы основные компоненты браузера:

  • Пользовательский интерфейс: сюда входит адресная строка, кнопки "Назад" и "Вперед", меню закладок и т. д. По сути, это все части экрана браузера, за исключением окна, в котором вы видите саму веб-страницу.
  • Механизм браузера: он обрабатывает взаимодействие между пользовательским интерфейсом и механизмом визуализации.
  • Механизм обработки: он отвечает за отображение веб-страницы. Механизм визуализации анализирует HTML и CSS и отображает проанализированное содержимое на экране.
  • Сеть: это сетевые вызовы, такие как запросы XHR, сделанные с использованием различных реализаций для разных платформ, которые находятся за независимым от платформы интерфейсом. Мы говорили о сетевом уровне более подробно в предыдущем посте этой серии.
  • Бэкэнд пользовательского интерфейса: он используется для рисования основных виджетов, таких как флажки и окна. Этот бэкэнд предоставляет общий интерфейс, не зависящий от платформы. Он использует методы пользовательского интерфейса операционной системы внизу.
  • Механизм JavaScript. Мы подробно рассмотрели этот вопрос в предыдущем посте из этой серии. По сути, здесь выполняется JavaScript.
  • Сохранение данных: вашему приложению может потребоваться хранить все данные локально. Поддерживаемые типы механизмов хранения включают localStorage, indexDB, WebSQL и FileSystem.

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

Обзор движка рендеринга

Основная ответственность механизма рендеринга - отображать запрошенную страницу на экране браузера.

Механизмы рендеринга могут отображать документы и изображения HTML и XML. Если вы используете дополнительные плагины, движки также могут отображать различные типы документов, например PDF.

Механизмы рендеринга

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

  • Gecko - Firefox
  • WebKit - Safari
  • Blink - Chrome, Opera (начиная с версии 15)

Процесс рендеринга

Механизм визуализации получает содержимое запрошенного документа от сетевого уровня.

Построение DOM-дерева

Первым шагом механизма визуализации является анализ HTML-документа и преобразование проанализированных элементов в фактические узлы DOM в дереве DOM.

Представьте, что у вас есть следующий текстовый ввод:

Дерево DOM для этого HTML будет выглядеть так:

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

Построение дерева CSSOM

CSSOM относится к объектной модели CSS. Пока браузер строил DOM страницы, он обнаружил тег link в разделе head, который ссылался на внешнюю theme.css таблицу стилей CSS. Предвидя, что ему может понадобиться этот ресурс для отображения страницы, он немедленно отправил запрос на него. Представим, что файл theme.css имеет следующее содержимое:

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

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

Давайте поработаем с конкретным примером, который мы привели. Любой текст, содержащийся в теге span, который помещается в элемент body, имеет размер шрифта 16 пикселей и красный цвет. Эти стили унаследованы от элемента body. Если элемент span является дочерним элементом элемента p, его содержимое не отображается из-за применения к нему более специфических стилей.

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

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

Визуальные инструкции в HTML в сочетании с данными стиля из дерева CSSOM используются для создания дерева рендеринга.

Вы спросите, что такое дерево рендеринга? Это дерево визуальных элементов, построенное в том порядке, в котором они будут отображаться на экране. Это визуальное представление HTML вместе с соответствующим CSS. Назначение этого дерева - дать возможность раскрасить содержимое в правильном порядке.

Каждый узел в дереве визуализации известен как средство визуализации или объект визуализации в Webkit.

Вот как будет выглядеть дерево рендерера вышеупомянутых деревьев DOM и CSSOM:

Чтобы построить дерево рендеринга, браузер делает примерно следующее:

  • Начиная с корня DOM-дерева, он проходит каждый видимый узел. Некоторые узлы не видны (например, теги сценария, метатеги и т. Д.) И опускаются, поскольку они не отражаются в визуализированном выводе. Некоторые узлы скрыты с помощью CSS и также не отображаются в дереве рендеринга. Например, узел диапазона - в приведенном выше примере он отсутствует в дереве рендеринга, потому что у нас есть явное правило, устанавливающее для него свойство display: none.
  • Для каждого видимого узла браузер находит соответствующие совпадающие правила CSSOM и применяет их.
  • Он испускает видимые узлы с содержимым и их вычисленными стилями.

Вы можете посмотреть исходный код RenderObject (в WebKit) здесь: https://github.com/WebKit/webkit/blob/fde57e46b1f8d7dde4b2006aaf7ebe5a09a6984b/Source/WebCore/rendering/RenderObject.h

Давайте просто посмотрим на некоторые из основных вещей для этого класса:

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

Макет дерева рендеринга

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

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

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

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

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

Рисование дерева рендеринга

На этом этапе выполняется обход дерева средства визуализации и вызывается метод paint() средства визуализации для отображения содержимого на экране.

Раскрашивание может быть глобальным или инкрементным (аналогично макету):

  • Global - перерисовывается все дерево.
  • Инкрементальный - только некоторые средства визуализации изменяются таким образом, что не влияет на все дерево. Средство визуализации делает недействительным свой прямоугольник на экране. Это заставляет ОС рассматривать его как область, которую необходимо перерисовать, и генерирует событие paint. ОС делает это разумно, объединяя несколько регионов в один.

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

Порядок обработки скриптов и таблиц стилей

Сценарии анализируются и выполняются немедленно, когда синтаксический анализатор достигает тега <script>. Анализ документа останавливается до тех пор, пока сценарий не будет выполнен. Это означает, что процесс синхронный.

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

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

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

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

  1. JavaScript - в предыдущих сообщениях мы рассмотрели тему написания оптимизированного кода, который не блокирует пользовательский интерфейс, эффективно использует память и т. д. Когда дело доходит до рендеринга, нам нужно подумать о том, как ваш код JavaScript будет взаимодействовать с элементами DOM на странице. JavaScript может внести множество изменений в пользовательский интерфейс, особенно в SPA.
  2. Расчет стиля - это процесс определения того, какое правило CSS к какому элементу применяется, на основе совпадающих селекторов. Как только правила определены, они применяются и рассчитываются окончательные стили для каждого элемента.
  3. Макет: как только браузер узнает, какие правила применяются к элементу, он может начать вычислять, сколько места занимает последний и где он находится на экране браузера. Модель макета сети определяет, что один элемент может влиять на другие. Например, ширина <body> может влиять на ширину его дочерних элементов и так далее. Все это означает, что процесс верстки требует больших вычислений. Рисунок выполнен в несколько слоев.
  4. Paint - здесь заполняются фактические пиксели. Процесс включает в себя рисование текста, цветов, изображений, границ, теней и т. Д. - каждой визуальной части каждого элемента.
  5. Составление - поскольку части страницы были нарисованы в несколько слоев, их необходимо нарисовать на экране в правильном порядке, чтобы страница отображалась правильно. Это очень важно, особенно для перекрывающихся элементов.

Оптимизация вашего JavaScript

JavaScript часто вызывает визуальные изменения в браузере. Тем более при строительстве СПА.

Вот несколько советов о том, какие части вашего JavaScript можно оптимизировать для улучшения рендеринга:

  • Избегайте setTimeout или setInterval для визуальных обновлений. Они вызовут callback в какой-то момент кадра, возможно, в самом конце. Что мы хотим сделать, так это вызвать визуальное изменение в самом начале кадра, чтобы не пропустить его.
  • Перенесите длительные вычисления JavaScript в Web Workers, как мы обсуждали ранее.
  • Используйте микрозадачи для внесения изменений DOM в несколько кадров. Это в случае, если задачам нужен доступ к DOM, который недоступен для Web Workers. По сути, это означает, что вы разбиваете большую задачу на более мелкие и выполняете их внутри requestAnimationFrame, setTimeout, setInterval в зависимости от характера задачи.

Оптимизируйте свой CSS

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

Чтобы оптимизировать рендеринг, примите во внимание следующее:

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

Оптимизировать макет

Пересчет макета может быть очень тяжелым для браузера. Рассмотрим следующие оптимизации:

  • По возможности уменьшайте количество макетов. Когда вы меняете стили, браузер проверяет, требует ли какое-либо из изменений пересчета макета. Для изменения таких свойств, как ширина, высота, слева, сверху и в целом свойства, связанные с геометрией, требуется макет. Поэтому старайтесь не менять их как можно больше.
  • По возможности используйте flexbox вместо старых моделей компоновки. Он работает быстрее и может дать вашему приложению огромное преимущество в производительности.
  • Избегайте принудительных синхронных макетов. Следует иметь в виду, что пока выполняется JavaScript, все значения старого макета из предыдущего кадра известны и доступны для запроса. Если вы получите доступ к box.offsetHeight, это не будет проблемой. Однако, если вы измените стили поля до того, как к нему будет осуществлен доступ (например, путем динамического добавления некоторого класса CSS к элементу), браузеру придется сначала применить изменение стиля, а затем запустить макет. Это может занять очень много времени и ресурсов, поэтому по возможности избегайте этого.

Оптимизируйте краску

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

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

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

Есть бесплатный план, если вы хотите попробовать SessionStack.

Ресурсы