Пошаговое руководство по использованию 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 мы можем сгенерировать сравнение «до» и «после», чтобы визуализировать разницу и измерить новую степень нестабильности макета:

[
{
"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 , который включает данные о кумулятивном смещении вёрстки, полученные на основе реальных данных пользователей миллионов веб-сайтов. Он позволяет вам оценить эффективность вашей работы (или работы ваших конкурентов) или использовать его для анализа состояния нестабильности вёрстки в интернете.