Missed the action at this year's Chrome Dev Summit? Catch up with our playlist on YouTube. Watch now.

Не используйте большие сложные макеты и избегайте подтормаживания макетов

Макет ― это источник, из которого браузеры получают информацию о геометрии элементов: об их размере и расположении на странице. Для каждого элемента будет предоставлена явная или скрытая информация о размере, основанная на использовавшемся CSS, содержимом элемента или о родительском элементе. В браузерах Chrome, Opera, Safari и в Internet Explorer этот процесс называется Layout (Перерасчет макета). В Firefox он называется Reflow (Перерасчет дерева отрисовки), но по сути это один и тот же процесс

TL;DR

  • Перерасчет макета обычно выполняется для всего документа целиком.
  • Количество элементов DOM влияет на производительность, поэтому при любой возможности следует избегать вызова операции перерасчета макета.
  • Оценивайте производительность модели макета; новый Flexbox обычно быстрее старого Flexbox или моделей макетов на основе float.
  • Избегайте принудительного синхронного перерасчета макета и подтормаживания макета; сначала следует прочитать значения стилей, а затем вносить изменения в стили.

Как и в отношении расчета стилей, на потребление ресурсов при перерасчете макета влияют следующие факторы:

  1. Количество элементов, которые требуется рассчитать в макете.
  2. Степень сложности этих макетов.

При любой возможности избегайте перерасчета макета

При изменении стилей браузер проверяет, требуется ли для какого-либо из изменений перерасчет дерева визуализации. Изменение всех "геометрических свойств", таких как width, height, left, или top влечет за собой перерасчет макета.

.box {
  width: 20px;
  height: 20px;
}

/**
 * Changing width and height
 * triggers layout.
 */
.box--expanded {
  width: 200px;
  height: 350px;
}

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

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

DevTools показывает, что на перерасчет макета потребуется много времени

При разборе кадра из приведенного выше примера мы видим, что на перерасчет макета тратится более 20 мс, а это очень много с учетом того, что у нас есть 16 мс для вывода кадра на экран в анимации. Также видно, что программа DevTools сообщает размер дерева визуализации (в данном случае это 1618 элементов) и количество узлов, для которых его требовалось перерасчитать.

Используйте модуль flexbox для старых моделей макета

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

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

Верстка страниц с использованием плавающих элементов

Если обновить этот образец с использованием Flexbox, последнего новшества в веб-платформе, мы получим другую картину:

Верстка страниц с использованием flexbox

Теперь на перерасчет макета для такого же числа элементов и такого же внешнего вида требуется намного меньше времени (в данном случае 3,5 мс против 14 мс). Важно помнить, что в некоторых ситуациях может оказаться, что использовать Flexbox невозможно, поскольку он поддерживается совсем не так широко, как float, однако при любой возможности следует хотя бы проанализировать влияние модели макета на производительность и выбрать ту из них, на реализацию которой требуется меньше ресурсов.

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

Не используйте принудительный синхронный перерасчет макета

Вывод кадра на экран выполняется в следующем порядке:

Верстка страниц с использованием flexbox

Сначала выполняется JavaScript, затем вычисление стилей, _затем_перерасчет макета. Однако с помощью JavaScript можно заставит браузер сначала выполнить перерасчет макета. Это называется принудительным синхронным перерасчетом макета.

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

// Schedule our function to run at the start of the frame.
requestAnimationFrame(logBoxHeight);

function logBoxHeight() {
  // Gets the height of the box in pixels and logs it out.
  console.log(box.offsetHeight);
}

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

function logBoxHeight() {

  box.classList.add('super-big');

  // Gets the height of the box in pixels
  // and logs it out.
  console.log(box.offsetHeight);
}

Теперь, чтобы ответить на вопрос о высоте, браузер должен сначала применить изменение стиля (из-за добавления класса super-big), а затем выполнить перерасчет макета. Только после этого он сможет вернуть правильное значение высоты. Это ненужная и потенциально ресурсоемкая работа.

Из-за этого всегда следует объединять в пакет операции чтения стилей и выполнять сначала их (когда браузер может использовать значения макета из предыдущего кадра), и только затем выполнять операции записи:

Если правильно написать эту функцию, она будет иметь следующий вид:

function logBoxHeight() {
  // Gets the height of the box in pixels
  // and logs it out.
  console.log(box.offsetHeight);

  box.classList.add('super-big');
}

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

Избегайте подтормаживания макетов

Есть способ сделать синхронный перерасчет макета еще более плохим: быстро выполнить несколько таких перерасчетов подряд. Взгляните на этот код:

function resizeAllParagraphsToMatchBlockWidth() {

  // Puts the browser into a read-write-read-write cycle.
  for (var i = 0; i < paragraphs.length; i++) {
    paragraphs[i].style.width = box.offsetWidth + 'px';
  }
}

Этот код проходит по группе абзацев и задает ширину каждого из них в соответствии с шириной элемента "поле". Выглядит довольно безвредно, однако проблема заключается в том, что при каждом проходе выполняется считывание значения стиля (box.offsetWidth), после чего оно сразу же используется для изменения ширины абзаца (paragraphs[i].style.width). При следующем проходе браузер должен будет учесть, что стили с момента последнего запроса значения offsetWidth изменились (при предыдущем проходе), поэтому он должен применить изменения стилей и выполнить перерасчет макета. И это будет происходить при каждом проходе!.

В данном образце, чтобы исправить эту ситуацию, нужно опять же считывать, а затем записывать значения:

// Read.
var width = box.offsetWidth;

function resizeAllParagraphsToMatchBlockWidth() {
  for (var i = 0; i < paragraphs.length; i++) {
    // Now write.
    paragraphs[i].style.width = width + 'px';
  }
}

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