Исправление нестабильности макета

Пошаговое руководство по использованию WebPageTest для выявления и устранения проблем нестабильности макета.

В предыдущей публикации я писал об измерении кумулятивного сдвига макета (CLS) в WebPageTest. CLS — это совокупность всех сдвигов макета, поэтому в этой публикации я подумал, что будет интересно изучить вопрос глубже и проанализировать каждый отдельный сдвиг макета на странице, чтобы попытаться понять, что может быть причиной нестабильности, и попытаться исправить проблему(ы).

Измерение сдвигов макета

Используя API нестабильности макета, мы можем получить список всех событий смены макета на странице:

new Promise(resolve => {
  new PerformanceObserver(list => {
    resolve(list.getEntries().filter(entry => !entry.hadRecentInput));
  }).observe({type: "layout-shift", buffered: true});
}).then(console.log);

Это создает массив сдвигов макета, которым не предшествуют события ввода:

[
  {
    "name": "",
    "entryType": "layout-shift",
    "startTime": 210.78500000294298,
    "duration": 0,
    "value": 0.0001045969445437389,
    "hadRecentInput": false,
    "lastInputTime": 0
  }
]

В этом примере наблюдалось одно очень маленькое смещение в 0,01% при 210 мс.

Знание времени и степени изменения полезно для уточнения возможной причины. Вернёмся к WebPageTest , чтобы провести дополнительные испытания в лабораторной среде.

Измерение сдвигов макета в WebPageTest

Подобно измерению CLS в WebPageTest, для измерения отдельных сдвигов макета потребуется специальная метрика. К счастью, теперь, когда Chrome 77 стал стабильным, этот процесс стал проще. API Layout Instability включен по умолчанию, поэтому вы сможете выполнить этот фрагмент JavaScript на любом сайте в Chrome 77 и получить результаты немедленно. В WebPageTest вы можете использовать браузер Chrome по умолчанию и не беспокоиться о флагах командной строки или использовании Canary.

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

[LayoutShifts]
return new Promise(resolve => {
  new PerformanceObserver(list => {
    resolve(JSON.stringify(list.getEntries().filter(entry => !entry.hadRecentInput)));
  }).observe({type: "layout-shift", buffered: true});
});

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

Веб-сайт, который я буду использовать для теста, — ismyhostfastyet.com . Его я создал для сравнения реальной производительности загрузки веб-хостингов.

Выявление причин нестабильности макета

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

[
  {
    "name": "",
    "entryType": "layout-shift",
    "startTime": 3087.2349999990547,
    "duration": 0,
    "value": 0.3422101449275362,
    "hadRecentInput": false,
    "lastInputTime": 0
  }
]

Подводя итог, можно сказать, что происходит одно смещение макета на 34,2% за 3087 мс. Чтобы определить причину, воспользуемся представлением в виде киноленты WebPageTest.

Две ячейки в ленте, показывающие снимки экрана до и после смены макета.
Две ячейки в ленте, показывающие снимки экрана до и после смены макета.

Прокрутив ленту примерно до трёх секунд, мы видим причину смещения макета на 34%: цветную таблицу. Сайт асинхронно загружает JSON-файл, а затем отображает его в таблице. Изначально таблица пуста, поэтому смещение вызвано ожиданием её заполнения при загрузке результатов.

Заголовок веб-шрифта, появляющийся из ниоткуда.
Заголовок веб-шрифта, появляющийся из ниоткуда.

Но это ещё не всё. Когда страница визуально завершает работу примерно через 4,3 секунды, мы видим, как внезапно появляется тег <h1> страницы «Мой хост уже работает?». Это происходит потому, что сайт использует веб-шрифт и не предпринимает никаких мер по оптимизации рендеринга. Макет при этом, по-видимому, не смещается, но всё равно неудобно ждать так долго, чтобы прочитать заголовок.

Исправление нестабильности макета

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

Вот код для генерации данных-заполнителей:

function getRandomFiller(maxLength) {
  var filler = '█';
  var len = Math.ceil(Math.random() * maxLength);
  return new Array(len).fill(filler).join('');
}

function getRandomDistribution() {
  var fast = Math.random();
  var avg = (1 - fast) * Math.random();
  var slow = 1 - (fast + avg);
  return [fast, avg, slow];
}

// Temporary placeholder data.
window.data = [];
for (var i = 0; i < 36; i++) {
  var [fast, avg, slow] = getRandomDistribution();
  window.data.push({
    platform: getRandomFiller(10),
    client: getRandomFiller(5),
    n: getRandomFiller(1),
    fast,
    avg,
    slow
  });
}
updateResultsTable(sortResults(window.data, 'fast'));

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

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

Вот как выглядят заполнители во время загрузки данных JSON:

Таблица данных отображается с использованием заполнителей.
Таблица данных отображается с использованием заполнителей.

Решить проблему с веб-шрифтом гораздо проще. Поскольку сайт использует Google Fonts, нам нужно просто передать свойство display=swap в CSS-запрос. Вот и всё. API Fonts добавит стиль font-display: swap в объявление шрифта, что позволит браузеру немедленно отображать текст резервным шрифтом. Вот соответствующая разметка с исправлением:

<link href="https://fonts.googleapis.com/css?family=Chivo:900&display=swap" rel="stylesheet">

Проверка оптимизаций

После повторного запуска страницы через WebPageTest мы можем сгенерировать сравнение «до» и «после», чтобы визуализировать разницу и измерить новую степень нестабильности макета:

Видеолента WebPageTest, демонстрирующая загрузку обоих сайтов одновременно с оптимизацией макета и без нее.
Видеолента WebPageTest, демонстрирующая загрузку обоих сайтов одновременно с оптимизацией макета и без нее.
[
  {
    "name": "",
    "entryType": "layout-shift",
    "startTime": 3070.9349999997357,
    "duration": 0,
    "value": 0.000050272187989256116,
    "hadRecentInput": false,
    "lastInputTime": 0
  }
]

Согласно пользовательской метрике , сдвиг макета всё ещё происходит на 3071 мс (примерно в то же время, что и раньше), но его интенсивность гораздо меньше: 0,005%. Я могу с этим смириться.

Из диафильма также ясно, что шрифт <h1> немедленно заменяется системным шрифтом, что позволяет пользователям быстрее его прочесть.

Заключение

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

(Еще кое-что) Измерение нестабильности макета, с которой сталкиваются реальные пользователи

Приятно иметь возможность запустить WebPageTest на странице до и после оптимизации и увидеть улучшение показателей, но главное — это то, что пользовательский опыт действительно улучшается. Разве не поэтому мы изначально стремимся улучшить сайт?

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

Помимо сбора собственных данных о нестабильности вёрстки, ознакомьтесь с отчётом Chrome UX Report , который включает данные о кумулятивном смещении вёрстки, полученные на основе реальных данных пользователей миллионов веб-сайтов. Он позволяет вам оценить эффективность вашей работы (или работы ваших конкурентов) или использовать его для анализа состояния нестабильности вёрстки в интернете.