Что значит ошибка рендеринга страницы

Рендеринг WEB-страницы: что об этом должен знать front-end разработчик

Приветствую вас, уважаемые хабравчане! Сегодня я бы хотел осветить вопрос рендеринга в веб-разработке. Конечно, на эту тему уже написано много статей, но, как мне показалась, вся информация довольно разрознена и отрывочна. По крайней мере, чтобы собрать всю картину в своей голове и осмыслить её, мне пришлось проанализировать немало информации (в основном — англоязычной). Именно поэтому я решил формализовать свои знания в статью, и поделиться результатом с сообществом Хабра. Думаю, информация будет полезна как начинающим веб-разработчикам, так и более опытным, чтобы освежить и структурировать свои знания.

Данное направление можно и нужно оптимизировать на этапе вёрстки/frontend-разработки, поскольку, очевидно, что разметка, стили и скрипты принимают в рендеринге непосредственное участие. Для этого соответствующие специалисты должны знать некоторые тонкости.

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

Процесс обработки WEB-страницы браузером

Для начала, рассмотрим последовательность работы браузера при отображении документа:

  1. Из полученного от сервера HTML-документа формируется DOM (Document Object Model).
  2. Загружаются и распознаются стили, формируется CSSOM (CSS Object Model).
  3. На основе DOM и CSSOM формируется дерево рендеринга, или render tree — набор объектов рендеринга (Webkit использует термин «renderer», или «render object», а Gecko — «frame»). Render tree дублирует структуру DOM, но сюда не попадают невидимые элементы (например — , или элементы со стилем display:none; ). Также, каждая строка текста представлена в дереве рендеринга как отдельный renderer. Каждый объект рендеринга содержит соответствующий ему объект DOM (или блок текста), и рассчитанный для этого объекта стиль. Проще говоря, render tree описывает визуальное представление DOM.
  4. Для каждого элемента render tree рассчитывается положение на странице — происходит layout. Браузеры используют поточный метод (flow), при котором в большинстве случаев достаточно одного прохода для размещения всех элементов (для таблиц проходов требуется больше).
  5. Наконец, происходит отрисовка всего этого добра в браузере — painting.
Читайте также:  Булавка под простыней что значит

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

Repaint

В случае изменения стилей элемента, не влияющих на его размеры и положение на странице (например, background-color , border-color , visibility ), браузер просто отрисовывает его заново, с учётом нового стиля — происходит repaint (или restyle).

Reflow

Если же изменения затрагивают содержимое, структуру документа, положение элементов — происходит reflow (или relayout). Причинами таких изменений обычно являются:

  • Манипуляции с DOM (добавление, удаление, изменение, перестановка элементов);
  • Изменение содержимого, в т.ч. текста в полях форм;
  • Расчёт или изменение CSS-свойств;
  • Добавление, удаление таблиц стилей;
  • Манипуляции с атрибутом « class »;
  • Манипуляции с окном браузера — изменения размеров, прокрутка;
  • Активация псевдо-классов (например, :hover ).

Оптимизация со стороны браузера

Браузеры по возможности локализуют repaint и reflow в пределах элементов, подвергнувшимися изменению. Например, изменение размеров абсолютно или фиксировано спозиционированного элемента затронет только сам элемент и его потомков, в то время как изменение статично спозиционированного — повлечет reflow всех элементов, следующих за ним.

Ещё одна особенность — во время выполнения JavaScript браузеры кэшируют вносимые изменения, и применяют их в один проход по завершению работы блока кода. Например, в ходе выполнения данного кода произойдет только один reflow и repaint:

Однако, как описано выше, обращение к свойствам элементов вызовет принудительный reflow. То есть, если мы в приведённый блок кода добавим обращение к свойству элемента, это вызовет лишний reflow:

В итоге мы получим 2 reflow вместо одного. Поэтому, обращения к свойствам элементов по возможности нужно группировать в одном месте, дабы оптимизировать производительность (см. более подробный пример на JSBin).

Но, на практике встречаются ситуации, когда без принудительного reflow не обойтись. Допустим, у нас есть задача: к элементу нужно применить одно и то же свойство (возьмём « margin-left ») сначала без анимации (установить в 100px ), а затем — анимировать посредством transition в значение 50px . Можете сразу посмотреть этот пример на JSBin, но я распишу его и тут.
Для начала заведём класс с transition:

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

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

Практические советы по оптимизации

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

  • Пишите валидный HTML и CSS, с указанием кодировки. Стили лучше включать в , а скрипты — в конце .
  • Стремитесь упрощать и оптимизировать селекторы CSS (этим часто пренебрегают разработчики, использующие препроцессоры). Чем меньше вложенность — тем лучше. По эффективности обработки селекторы можно расположить в следующем порядке (начиная с наиболее быстрого):
    1. Идентификатор: #id
    2. Класс: .class
    3. Тэг: div
    4. Соседний селектор: a + i
    5. Дочерний селектор: ul > li
    6. Универсальный селектор: *
    7. Селектор атрибутов: input[type=»text»]
    8. Всевдоэлементы и псевдоклассы: a:hover

    Следует помнить, что браузер обрабатывает селекторы справа налево, поэтому в качестве ключевого (крайнего правого) селектора лучше использовать наиболее эффективные — идентификатор и класс.

  • В скриптах минимизируйте любую работу с DOM. Кэшируйте всё: свойства, объекты, если подразумевается повторное их использование. При сложных манипуляциях разумно работать с «offline» элементом (т.е. который находится не в DOM, а в памяти), с последующим помещением его в DOM.
  • При использовании jQuery для выборки элементов придерживайтесь рекомендаций по составлению селекторов.
  • Для изменения стилей элементов лучше модифицировать только атрибут « class », и как можно глубже в дереве DOM, это и более грамотно с точки зрения разработки и поддержки (отделение логики от представления), и менее затратно для браузера.
  • Анимировать желательно только абсолютно и фиксировано спозиционированные элементы.
  • Можно отключать сложные :hover анимации во время скроллинга (например, добавляя к body класс « no-hover »). Статья на эту тему.
  • Надеюсь, каждый читатель извлёк из статьи что-нибудь полезное. В любом случае — спасибо за внимание!

    UPD: Спасибо SelenIT2 и piumosso за верные замечания по поводу эффективности обработки CSS-селекторов.

    Источник

    5 хитростей по устранению ресурсов, блокирующих рендеринг

    Ресурсы блокировки рендеринга — это статические файлы, такие как файлы шрифтов, HTML, CSS и JavaScript, которые жизненно важны для процесса рендеринга веб-страницы. Когда браузер сталкивается с ресурсом блокировки рендеринга, он прекращает загрузку остальных ресурсов до тех пор, пока эти критические файлы не будут обработаны. Тем временем весь процесс рендеринга приостанавливается. С другой стороны, ресурсы, не блокирующие рендеринг, не откладывают рендеринг страницы. Браузер может безопасно загрузить их в фоновом режиме после отрисовки начальной страницы.

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

    Зачем устранять ресурсы, блокирующие рендеринг?

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

    Есть три способа уменьшить количество и влияние ресурсов, блокирующих рендеринг:

    1. Сделайте их не блокирующими рендеринг ресурсами, отложив их загрузку
    2. Уменьшите общее количество ресурсов, блокирующих рендеринг, используя такие методы, как объединение (это также означает меньше HTTP-запросов)
    3. Уменьшите размер ресурса с помощью минификации, чтобы на странице было меньше байтов для загрузки

    Типы ресурсов, блокирующих рендеринг

    Как правило, браузер обрабатывает все, что находит в разделе HTML-страницы, как блокировку рендеринга. Это включает в себя:

    1. Таблицы стилей CSS
    2. Файлы JavaScript добавленные в раздел
    3. Шрифты, добавленные либо из CDN, либо с локального сервера
    4. Импорт HTML (хотя импорт HTML теперь устарел, вы все еще можете встретить его на старых страницах)

    Изображения, медиафайлы и теги

    Отложенные сценарии следуют порядку документов, как неотложные сценарии по умолчанию. Например, в приведенном выше примере, script01.js будет выполняться первым, независимо от того, какой скрипт загружается первым. Вы не можете добавить defer к встроенным скриптам; он работает только с внешними скриптами, которые определяют местоположение скрипта с помощью атрибута src .

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

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

    Атрибут defer рекомендуется использовать для сценариев, которым требуется DOM, но вы хотите начать их загрузку до загрузки документа, не превращая их в блокирующий рендер ресурс. Вы также должны использовать defer , а не async , если порядок документов важен — например, когда последовательные сценарии зависят друг от друга.

    Атрибут async рекомендуется использовать для независимых сторонних скриптов, таких как рекламные объявления, трекеры и аналитические скрипты. Например, Google Analytics рекомендует добавить атрибут async для поддержки асинхронной загрузки в современных браузерах.

    4. Минимизируйте и объедините CSS и JavaScript.

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

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

    Существует множество инструментов, которые помогут вам выполнить минификацию и объединение в соответствии с передовыми практиками, включая Minify, CSS Minifier, Minify Code и PostCSS. Инструменты сборки, такие как webpack, Parcel и Rollup, имеют встроенные функции минификации, объединения и разделения кода, которые позволяют быстро уменьшить количество ресурсов, блокирующих рендеринг.

    5. Загрузите пользовательские шрифты локально.

    Поскольку пользовательские шрифты вызываются из раздела документа, они также блокируют отображение ресурсов. Например:

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

    Например, Google Fonts добавляет правила @font-face для всех наборов символов, с которыми идет гарнитура, таких как латиница, кириллица, китайский, вьетнамский и другие. Скажем, например, что онлайн-файл CSS, который вы добавляете с тегом
    , включает правила @font-face для семи различных наборов символов, но вы хотите использовать только один (например, латинский). Однако шрифты Google не загружают файлы шрифтов для всех наборов символов; они просто добавляют много избыточных правил @font-face в файл CSS.

    Если вы добавляете шрифты локально, вы также можете минимизировать свой CSS, связанный со шрифтами, и связать его с остальной частью вашего CSS. Вы можете использовать Google Web Fonts Helper для быстрого создания локальных правил @font-face для Google Fonts. Например, вот что вам нужно добавить, чтобы включить начертание шрифта Lato Regular:

    Обратите внимание, что Google Web Fonts Helper не добавляет правила font-display: swap; я сам добавил это в указанное выше объявление. Это дескриптор правила @font-face , позволяющего указать, как браузер должен отображать начертание шрифта на странице.

    Используя font-display со значением swap , вы даете команду браузеру немедленно начать использовать системный шрифт и заменить его пользовательским шрифтом после загрузки (это правило также добавляется, когда вы извлекаете шрифт из CDN Google). Это позволяет избежать невидимого текста на странице, пока пользовательский шрифт все еще загружается.

    Когда вы загружаете шрифты локально, убедитесь, что вы обслуживаете сжатые форматы шрифтов для современных браузеров, таких как WOFF и WOFF2. Помните, что более легкие файлы также уменьшают влияние ресурсов, блокирующих рендеринг. Помимо создания правил @font-face , Google Web Fonts Helper также позволяет загружать заархивированный файл, содержащий все нужные вам форматы шрифтов.

    Почему не следует загружать пользовательские шрифты асинхронно

    В некоторых статьях о ресурсах блокировки рендеринга рекомендуется использовать загрузчик веб-шрифтов TypeKit для асинхронной загрузки пользовательских шрифтов. Когда-то это был достойный инструмент, но он не обновлялся с 2017 года, и у него много нерешенных проблем. Я бы не рекомендовал его использовать.

    Хотя асинхронная загрузка шрифтов сокращает критический путь рендеринга, вы всегда должны делать это осторожно. Если шрифты загружаются позже, чем содержимое страницы, на странице может возникнуть общая проблема UX, называемая миганием невидимого текста (FOIT).

    Существуют различные способы, чтобы справиться с FOIT, например, использование сторонних библиотек или вышеупомянутое правило font-display: swap (см. поддержка браузера для font-display , плюс, обратите внимание, что использовать его с правилом swap просто превращает FOIT в FOUT — вспышка неокрашенного текста — но это не полностью устраняет проблему). Тем не менее, вы захотите потратить время на обдумывание того, действительно ли стоит идти по асинхронному маршруту с точки зрения производительности. Подумайте о весе дополнительных скриптов, потенциальных проблемах, пользователях с отключенным JavaScript (вам все равно нужно добавить статический элемент
    в теги

    Источник

    Оцените статью