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

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

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

Измерение изменений макета

Используя Layout Instability 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 нестабильности макета включен по умолчанию, поэтому вы сможете выполнить этот фрагмент JS на любом веб-сайте в 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.

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

Прокрутка диафильма до отметки ~3 секунды показывает нам, в чем именно причина сдвига макета на 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, нам просто нужно передать свойство display=swap в запросе CSS. Вот и все. API шрифтов добавит стиль 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 , который включает данные о совокупном сдвиге макета, основанные на реальном опыте пользователей на миллионах веб-сайтов. Это позволяет вам узнать, как вы (или ваши конкуренты) работаете, или вы можете использовать его для изучения состояния нестабильности макета в Интернете.