Совокупное смещение макета (CLS)
Вы когда-нибудь сталкивались с тем, что во время чтения статьи в Интернете внезапно что-то менялось? Без предупреждения текст смещался в сторону, и вы теряли читаемую строчку. Или, еще хуже: вы хотели нажать ссылку или кнопку, но за мгновение до этого, ссылка смещалась, и вы в конечном итоге нажимали что-то другое!
В большинстве случаев подобные ситуации просто раздражают, но иногда они могут нанести реальный ущерб.
Неожиданное перемещение содержимого страницы обычно происходит из-за асинхронной загрузки ресурсов или динамического добавления элементов DOM к странице поверх уже существующего контента. Причиной может быть изображение или видео с неизвестными размерами, шрифт, отображаемый крупнее или меньше, чем его запасной вариант, сторонние объявления или виджеты, которые динамически изменяют размер.
Ещё сильнее эта проблема усугубляется тем, что запланированная в процессе разработки функциональность сайта может сильно отличаться от реальной, воспринимаемой пользователями. Персонализированный или сторонний контент часто ведёт себя иначе, чем при разработке, например: тестовые изображения обычно уже находятся в кеше браузера разработчика, а вызовы API, которые выполняются локально, получают ответ сервера слишком быстро и возможные задержки не заметны.
Показатель CLS (Совокупное смещение макета) помогает решить эту проблему, измеряя, как часто такие проблемы случаются у реальных пользователей.
Что такое CLS? #
CLS — это показатель по временному окну с максимальными оценками смещения макета для каждого неожиданного смещения макета, которые происходят в течение всего времени жизни страницы.
Смещение макета происходит каждый раз, когда видимый элемент меняет свое положение от одного отрисованного фрейма к другому (см. ниже подробные сведения о том, как рассчитываются оценки смещения макета).
Временное окно смещений макета или окно сеансаэто период времени, который начинает отсчитываться в момент первого смещения макета и длится до выполнения одного из двух критериев: появления промежутка в 1 секунду или больше без смещений макета; достижения максимальной продолжительности временного окна, равной 5 секундам.
Временное окно с максимальными оценками смещения макета—это окно сеанса с максимальной совокупной оценкой всех сдвигов макета в этом окне.
Какое значение показателя CLS можно считать хорошим? #
Для обеспечения удобства работы пользователей сайты должны стремиться к тому, чтобы показатель CLS не превышал 0,1. Чтобы убедиться, что вы достигли этой цели для большинства пользователей, рекомендуется в качестве порогового значения использовать 75-й процентиль загрузки страниц, сегментированный по мобильным и настольным устройствам.
Подробно о смещениях макета #
Смещения макета определяются Layout Instability API (API нестабильности макета), который возвращает запись layout-shift
каждый раз, когда элемент, видимый в области просмотра, меняет свое начальное положение (например, верхнее и левое положение в значении по умолчанию свойства writing mode) между двумя фреймами. Такие элементы считаются нестабильными.
Обратите внимание, что смещения макета происходят только тогда, когда существующие элементы меняют свое положение. Если новый элемент добавляется в DOM-модель или существующий элемент меняет размер, это не будет считаться смещением макета до тех пор, пока данные изменения не заставят другие видимые элементы изменить свое начальное положение.
Оценка смещения макета #
Чтобы вычислить оценку смещения макета, браузер смотрит на размер области просмотра и перемещение нестабильных элементов в пределах области просмотра между двумя отрисованными фреймами. Оценка смещения макета—это результат умножения двух показателей этого движения: доли воздействия и доли расстояния (описаны ниже).
оценка смещения макета = доля воздействия * доля расстояния
Ударная фракция #
Доля воздействия измеряет, как нестабильные элементы влияют на область просмотра между двумя фреймами.
Объединение видимых областей всех нестабильных элементов для предыдущего и текущего фрейма в виде доли от общей площади области просмотра будет являться долей воздействия для текущего фрейма.
На изображении выше есть элемент, который занимает половину области просмотра в одном фрейме. Затем в следующем фрейме элемент сдвигается вниз на 25% от высоты области просмотра. Красный пунктирный прямоугольник указывает на объединение видимой области элемента в обоих фреймах, которая в данном случае составляет 75% от всего окна просмотра, поэтому доля воздействия составляет 0,75
.
Доля расстояния #
Другая часть выражения оценки смещения макета измеряет расстояние, на которое нестабильные элементы переместились относительно области просмотра. Доля расстоянияэто наибольшее расстояние, на которое любой нестабильный элемент переместился в фрейме (по горизонтали или по вертикали), деленное на наибольший размер области просмотра (ширина или высота, в зависимости от того, что больше).
В приведенном выше примере наибольший линейный размер области просмотра—это высота, а нестабильный элемент переместился на 25% от высоты области просмотра, поэтому доля расстояния составляет 0,25.
Итак, в этом примере доля воздействия составляет 0,75
, а доля расстояния0,25
, поэтому оценка смещения макета равна 0,75 * 0,25 = 0,1875
.
В следующем примере показано, как добавление содержимого к существующему элементу влияет на оценку смещения макета:
Кнопка «Щелкни меня!» (Click Me!) добавляется в нижнюю часть серого поля с черным текстом, который толкает зеленое поле с белым текстом вниз (и частично за пределы области просмотра).
В этом примере серый прямоугольник меняет размер, но его начальное положение не меняется, поэтому он не считается нестабильным элементом.
Кнопки «Щелкни меня!» ранее не было в DOM, поэтому ее начальное положение также не изменится.
Хотя начальное положение зеленого прямоугольника изменилось, но так как он был частично перемещен из области просмотра, невидимая область не учитывается при вычислении доли воздействия. Объединение видимых областей для зеленого прямоугольника в обоих фреймах (показано красным пунктирным прямоугольником) совпадает с площадью зеленого прямоугольника в первом кадре—50% области просмотра. Доля воздействия—0,5
.
Доля расстояния показана фиолетовой стрелкой. Зеленая рамка сместилась вниз примерно на 14% от области просмотра, поэтому доля расстояния составляет 0,14
.
Оценка смещения макета равна 0,5 x 0,14 = 0,07
.
Последний пример иллюстрирует несколько нестабильных элементов:
В первом фрейме выше представлены четыре результата запроса к API для животных, отсортированных в алфавитном порядке. Во втором фрейме в отсортированный список добавляется еще несколько результатов.
Первый элемент в списке (Cat, «Кот») не меняет свое начальное положение между фреймами, поэтому он стабильный. Точно так же новые элементы, добавленные в список, ранее не были в DOM, поэтому их начальные позиции также не меняются. Но остальные элементы Dog («Собака»), Horse («Лошадь») и Zebra («Зебра») меняют свои начальные позиции, что делает их нестабильными элементами.
Опять же, красные пунктирные прямоугольники представляют собой объединение этих трех нестабильных элементов до и после областей, которые в этом случае составляют около 38% площади области просмотра (доля воздействия равна 0,38
).
Стрелки показывают расстояния, на которые нестабильные элементы переместились из начальных положений. Элемент Zebra («Зебра»), обозначенный синей стрелкой, сместился больше прочих, примерно на 30% высоты области просмотра. Таким образом, доля расстояния в этом примере равна 0,3
.
Оценка смещения макета составляет 0,38 x 0,3 = 0,114
.
Ожидаемые и неожиданные смещения макета #
Не все сдвиги в макете—это плохо. Фактически многие динамические веб-приложения часто меняют начальное положение элементов на странице.
Изменения макета по инициативе пользователя #
Смещение макета плохо только в том случае, если пользователь этого не ожидает. С другой стороны, смещения макета, которые происходят в ответ на действия пользователя (щелчок по ссылке, нажатие кнопки, ввод в поле поиска и т. д.), как правило, допустимы, если смещение достаточно близко к взаимодействию, чтобы взаимосвязь между ними была понятна пользователю.
Например, если взаимодействие с пользователем запускает сетевой запрос, выполнение которого может занять некоторое время, лучше сразу освободить место и показать индикатор загрузки, чтобы избежать неприятного смещения макета после завершения запроса. Если пользователь не понимает, что что-то загружается, или не знает, когда ресурс будет готов, он может попытаться щелкнуть что-нибудь еще во время ожидания.
Для смещений макета, которые происходят в течение 500 миллисекунд после ввода пользователя, будет установлен флаг hadRecentInput
, поэтому их можно исключить из вычислений.
Анимации и переходы #
Если всё сделано правильно, анимация и переходыэто отличный способ обновить контент на странице, не удивляя пользователя. Резко и неожиданно перемещающийся контент на странице почти всегда создает неудобства для пользователей. Но контент, который постепенно и естественно перемещается из одной позиции в другую, часто помогает пользователю лучше понять, что происходит и что делать между изменениями состояния.
CSS-свойство transform
позволяет анимировать элементы, не вызывая смещений макета:
- Вместо изменения свойств высоты
height
и шириныwidth
можно использоватьtransform: scale()
. - Чтобы перемещать элементы, следует избегать изменения свойств
top
,right
,bottom
илиleft
и использовать вместо нихtransform: translate()
.
Как измерить CLS #
CLS можно измерить в лабораторных или полевых условиях с помощью следующих инструментов:.
Инструменты для измерения в полевых условиях #
- Отчет Chrome User Experience Report
- PageSpeed Insights
- Search Console (отчет Core Web Vitals report)
- JavaScript-библиотека
web-vitals
Инструменты для измерения в лабораторных условиях #
Измерение CLS в JavaScript #
- Chrome 77, Supported 77
- Firefox, Not supported
- Edge 79, Supported 79
- Safari, Not supported
Чтобы измерить CLS с помощью JavaScript, можно воспользоваться Layout Instability API (API нестабильности макета). В следующем примере показано, как создать PerformanceObserver
который прослушивает неожиданные записи смещений макета layout-shift
, группирует их в сеансы и регистрирует максимальное значение сеанса при каждом его изменении.
let clsValue = 0;
let clsEntries = [];
let sessionValue = 0;
let sessionEntries = [];
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
// Only count layout shifts without recent user input.
if (!entry.hadRecentInput) {
const firstSessionEntry = sessionEntries[0];
const lastSessionEntry = sessionEntries[sessionEntries.length - 1];
// If the entry occurred less than 1 second after the previous entry and
// less than 5 seconds after the first entry in the session, include the
// entry in the current session. Otherwise, start a new session.
if (sessionValue &&
entry.startTime - lastSessionEntry.startTime < 1000 &&
entry.startTime - firstSessionEntry.startTime < 5000) {
sessionValue += entry.value;
sessionEntries.push(entry);
} else {
sessionValue = entry.value;
sessionEntries = [entry];
}
// If the current session value is larger than the current CLS value,
// update CLS and the entries contributing to it.
if (sessionValue > clsValue) {
clsValue = sessionValue;
clsEntries = sessionEntries;
// Log the updated value (and its entries) to the console.
console.log('CLS:', clsValue, clsEntries)
}
}
}
}).observe({type: 'layout-shift', buffered: true});
В большинстве случаев текущее значение CLS на момент выгрузки страницы является окончательным значением CLS для этой страницы, но есть несколько важных исключений.
Далее приведем различия между тем, что сообщает API, и тем, как рассчитывается метрика.
Различия между метрикой и API #
- Если страница загружается в фоновом режиме или если она находилась в фоновом режиме до того, как браузер отрисовал любой контент, она не должна сообщать значение CLS.
- Если страница восстанавливается функцией back/forward cache, значение CLS данной страницы должно быть сброшено до нуля, поскольку пользователи воспринимают такие посещения страниц как отдельные.
- API не сообщает о записях
layout-shift
для смещений внутри iframe, но для правильного измерения CLS их нужно учитывать. Подфреймы могут использовать API, чтобы сообщать о своих записяхlayout-shift
в родительский фрейм для агрегирования.
Более того, CLS трудно определять из-за того, что показатель измеряется на протяжении всей продолжительности жизни страницы:
- Пользователи могут держать вкладку открытой в течение очень долгого временидней, недель, месяцев. Фактически пользователь может никогда не закрыть вкладку.
- В мобильных операционных системах браузеры обычно не запускают обратные вызовы выгрузки страницы для фоновых вкладок, что затрудняет сообщение «окончательного» значения.
Чтобы справиться с такими случаями, CLS следует сообщать каждый раз, когда страница находится в фоновом режиме, в дополнение к любому времени, когда она выгружается (событие visibilitychange
охватывает оба этих сценария). Системы аналитики, получающие эти данные, должны будут затем вычислить окончательное значение CLS на бэкенде.
Вместо того чтобы запоминать все эти тонкости, разработчики могут использовать для измерения CLS JavaScript-библиотеку web-vitals
, которая учитывает вышеупомянутые моменты:
import {onCLS} from 'web-vitals';
// Measure and log CLS in all situations
// where it needs to be reported.
onCLS(console.log);
Полный пример измерения CLS в JavaScript приводится в исходном коде onCLS()
.
Как улучшить показатель CLS #
Для большинства веб-сайтов можно избежать всех неожиданных смещений макета, придерживаясь нескольких основополагающих принципов:
- Всегда следует включать атрибуты размеров для изображений и видеоэлементов или иным образом резервировать необходимое пространство с помощью, например, CSS-свойства aspect-ratio. Такой подход гарантирует, что браузер сможет выделить правильный объем места в документе во время загрузки изображения. Кроме того, можно также применять политику функции unized-media, чтобы принудительно использовать это поведение в браузерах, поддерживающих политики функций.
- Никогда не следует вставлять контент поверх существующего, кроме случаев, когда это требуется в ответ на взаимодействие с пользователем. Это гарантирует, что любые изменения макета будут ожидаемыми.
- Лучше выбирать анимацию трансформации, чем анимацию изменения свойств, запускающих изменение макета. Нужно анимировать переходы таким образом, чтобы обеспечить контекст и непрерывность от состояния к состоянию.
Подробные сведения о том, как улучшить CLS, см. в статьях «Оптимизация CLS» и «Отладка смещений макета».
Дополнительные ресурсы #
- Руководство Google Publisher Tag по минимизации смещения макета
- Понимание совокупного смещения макета, Энни Салливан и Стив Кобес, #PerfMatters (2020)
CHANGELOG #
Occasionally, bugs are discovered in the APIs used to measure metrics, and sometimes in the definitions of the metrics themselves. As a result, changes must sometimes be made, and these changes can show up as improvements or regressions in your internal reports and dashboards.
To help you manage this, all changes to either the implementation or definition of these metrics will be surfaced in this CHANGELOG.
If you have feedback for these metrics, you can provide it in the web-vitals-feedback Google group.