---
layout: post
title: Совокупное смещение макета (CLS)
authors:
  - philipwalton
  - mihajlija
date: 2019-06-11
updated: 2022-10-19
description: В этой статье описывается метрика CLS (Совокупное смещение макета) и объясняются принципы ее измерения
tags:
  - performance
  - metrics
  - web-vitals
---

{% Aside 'caution' %} **1 июня 2021 г.:** Реализация CLS изменилась. Чтобы узнать больше о причинах изменения, прочтите статью [«Развитие метрики CLS»](/evolving-cls). {% endAside %}

{% Aside 'key-term' %} Совокупное смещение макета (CLS)—важный, ориентированный на пользователя показатель для измерения [визуальной стабильности,](/user-centric-performance-metrics/#types-of-metrics) так как он помогает количественно оценить, как часто пользователи сталкиваются с неожиданными сдвигами макета. Низкое значение показателя CLS говорит о том, что страница выглядит [восхитительно](/user-centric-performance-metrics/#questions). {% endAside %}

Вы когда-нибудь сталкивались с тем, что во время чтения статьи в Интернете внезапно что-то менялось? Без предупреждения текст смещался в сторону, и вы теряли читаемую строчку. Или, еще хуже: вы хотели нажать ссылку или кнопку, но за мгновение до этого, ссылка смещалась, и вы в конечном итоге нажимали что-то другое!

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

<figure>
  <video autoplay controls loop muted poster="https://storage.googleapis.com/web-dev-assets/layout-instability-api/layout-instability-poster.png" width="658" height="510">
    <source src="https://storage.googleapis.com/web-dev-assets/layout-instability-api/layout-instability2.webm" type="video/webm; codecs=vp8">
    <source src="https://storage.googleapis.com/web-dev-assets/layout-instability-api/layout-instability2.mp4" type="video/mp4; codecs=h264">
  </source></source></video>
  <figcaption>Видео, демонстрирующее негативные последствия нестабильности макета для пользователей.</figcaption></figure>

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

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

Показатель CLS (Совокупное смещение макета) помогает решить эту проблему, измеряя, как часто такие проблемы случаются у реальных пользователей.

## Что такое CLS?

CLS — это показатель по временному окну с максимальными *оценками смещения макета* для каждого [неожиданного](/cls/#expected-vs-unexpected-layout-shifts) смещения макета, которые происходят в течение всего времени жизни страницы.

*Смещение макета* происходит каждый раз, когда видимый элемент меняет свое положение от одного отрисованного фрейма к другому (см. ниже подробные сведения о том, как рассчитываются [оценки смещения макета](#layout-shift-score)).

Временное окно смещений макета или [*окно сеанса*](evolving-cls/#why-a-session-window)это период времени, который начинает отсчитываться в момент первого смещения макета и длится до выполнения одного из двух критериев: появления промежутка в 1 секунду или больше без смещений макета; достижения максимальной продолжительности временного окна, равной 5 секундам.

Временное окно с максимальными оценками смещения макета—это окно сеанса с максимальной совокупной оценкой всех сдвигов макета в этом окне.

<figure>
  <video controls autoplay loop muted width="658" height="452">
    <source src="https://storage.googleapis.com/web-dev-assets/better-layout-shift-metric/session-window.webm" type="video/webm">
    <source src="https://storage.googleapis.com/web-dev-assets/better-layout-shift-metric/session-window.mp4" type="video/mp4">
  </source></source></video>
  <figcaption>Пример окон сеанса. Синие полосы представляют собой оценки каждого отдельного сдвига макета.</figcaption></figure>

{% Aside 'caution' %} Ранее метрика CLS измеряла общую сумму *всех индивидуальных смещений макета*, произошедших в течение всего времени жизни страницы. Чтобы узнать, какие инструменты по-прежнему обеспечивают возможность сравнения с исходной реализацией метрики, ознакомьтесь со статьей [«Развитие метрики CLS (Совокупного смещения макета) в веб-инструментах»](/cls-web-tooling). {% endAside %}

### Какое значение показателя CLS можно считать хорошим?

Для обеспечения удобства работы пользователей сайты должны стремиться к тому, чтобы показатель CLS не превышал **0,1**. Чтобы убедиться, что вы достигли этой цели для большинства пользователей, рекомендуется в качестве порогового значения использовать **75-й процентиль** загрузки страниц, сегментированный по мобильным и настольным устройствам.

<picture>
  <source srcset="{{ "image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9mWVASbWDLzdBUpVcjE1.svg" | imgix }}" media="(min-width: 640px)" width="400", height="100">
  {% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uqclEgIlTHhwIgNTXN3Y.svg", alt="Хорошие значения CLS ниже 0,1, плохие значения больше 0,25 и все промежуточные значения требуют улучшения", width="400", height="300" %}
</picture>

{% Aside %} Чтобы узнать больше об исследованиях и методологии, лежащих в основе этой рекомендации, см. [«Определение пороговых значений показателей Core Web Vitals»](/defining-core-web-vitals-thresholds/) {% endAside %}

## Подробно о смещениях макета

Смещения макета определяются [Layout Instability API (API нестабильности макета)](https://github.com/WICG/layout-instability), который возвращает запись `layout-shift` каждый раз, когда элемент, видимый в области просмотра, меняет свое начальное положение (например, верхнее и левое положение в значении по умолчанию свойства [writing mode](https://developer.mozilla.org/docs/Web/CSS/writing-mode)) между двумя фреймами. Такие элементы считаются *нестабильными*.

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

### Оценка смещения макета

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

```text
оценка смещения макета = доля воздействия * доля расстояния
```

### Ударная фракция

[Доля воздействия](https://github.com/WICG/layout-instability#Impact-Fraction) измеряет, как *нестабильные элементы* влияют на область просмотра между двумя фреймами.

Объединение видимых областей всех *нестабильных элементов* для предыдущего *и* текущего фрейма в виде доли от общей площади области просмотра будет являться *долей воздействия* для текущего фрейма.

{% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BbpE9rFQbF8aU6iXN1U6.png", alt="Пример доли воздействия с одним *нестабильным элементом*", width="800", height="600", linkTo=true %}

На изображении выше есть элемент, который занимает половину области просмотра в одном фрейме. Затем в следующем фрейме элемент сдвигается вниз на 25% от высоты области просмотра. Красный пунктирный прямоугольник указывает на объединение видимой области элемента в обоих фреймах, которая в данном случае составляет 75% от всего окна просмотра, поэтому *доля воздействия* составляет `0,75`.

### Доля расстояния

Другая часть выражения оценки смещения макета измеряет расстояние, на которое нестабильные элементы переместились относительно области просмотра. *Доля расстояния*это наибольшее расстояние, на которое любой *нестабильный элемент* переместился в фрейме (по горизонтали или по вертикали), деленное на наибольший размер области просмотра (ширина или высота, в зависимости от того, что больше).

{% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASnfpVs2n9winu6mmzdk.png", alt="Пример доли расстояния с одним *нестабильным элементом*", width="800", height="600", linkTo=true %}

В приведенном выше примере наибольший линейный размер области просмотра—это высота, а нестабильный элемент переместился на 25% от высоты области просмотра, поэтому *доля расстояния* составляет 0,25.

Итак, в этом примере *доля воздействия* составляет `0,75`, а *доля расстояния*`0,25`, поэтому *оценка смещения макета* равна `0,75 * 0,25 = 0,1875`.

{% Aside %} Первоначально оценка смещения макета рассчитывалась только на основании *доли воздействия*. *Доля расстояния* была введена, чтобы избежать чрезмерного значения показателя в случаях, когда большие элементы смещаются на небольшую величину. {% endAside %}

В следующем примере показано, как добавление содержимого к существующему элементу влияет на оценку смещения макета:

{% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xhN81DazXCs8ZawoCj0T.png", alt="Пример смещения макета со стабильными и *нестабильными элементами* и обрезкой области просмотра", width="800", height="600", linkTo=true %}

Кнопка «Щелкни меня!» (Click Me!) добавляется в нижнюю часть серого поля с черным текстом, который толкает зеленое поле с белым текстом вниз (и частично за пределы области просмотра).

В этом примере серый прямоугольник меняет размер, но его начальное положение не меняется, поэтому он не считается *нестабильным элементом*.

Кнопки «Щелкни меня!» ранее не было в DOM, поэтому ее начальное положение также не изменится.

Хотя начальное положение зеленого прямоугольника изменилось, но так как он был частично перемещен из области просмотра, невидимая область не учитывается при вычислении *доли воздействия*. Объединение видимых областей для зеленого прямоугольника в обоих фреймах (показано красным пунктирным прямоугольником) совпадает с площадью зеленого прямоугольника в первом кадре—50% области просмотра. *Доля воздействия*—`0,5`.

*Доля расстояния* показана фиолетовой стрелкой. Зеленая рамка сместилась вниз примерно на 14% от области просмотра, поэтому *доля расстояния* составляет `0,14`.

Оценка смещения макета равна `0,5 x 0,14 = 0,07`.

Последний пример иллюстрирует несколько *нестабильных элементов*:

{% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FdCETo2dLwGmzw0V5lNT.png", alt="Пример смещения макета с несколькими стабильными и *нестабильными элементами*", width="800", height="600", linkTo=true %}

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

Первый элемент в списке (Cat, «Кот») не меняет свое начальное положение между фреймами, поэтому он стабильный. Точно так же новые элементы, добавленные в список, ранее не были в DOM, поэтому их начальные позиции также не меняются. Но остальные элементы Dog («Собака»), Horse («Лошадь») и Zebra («Зебра») меняют свои начальные позиции, что делает их *нестабильными элементами*.

Опять же, красные пунктирные прямоугольники представляют собой объединение этих трех *нестабильных элементов* до и после областей, которые в этом случае составляют около 38% площади области просмотра (*доля воздействия* равна `0,38`).

Стрелки показывают расстояния, на которые *нестабильные элементы* переместились из начальных положений. Элемент Zebra («Зебра»), обозначенный синей стрелкой, сместился больше прочих, примерно на 30% высоты области просмотра. Таким образом, *доля расстояния* в этом примере равна `0,3`.

Оценка смещения макета составляет `0,38 x 0,3 = 0,114`.

### Ожидаемые и неожиданные смещения макета

Не все сдвиги в макете—это плохо. Фактически многие динамические веб-приложения часто меняют начальное положение элементов на странице.

#### Изменения макета по инициативе пользователя

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

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

Для смещений макета, которые происходят в течение 500 миллисекунд после ввода пользователя, будет установлен флаг [`hadRecentInput`](https://wicg.github.io/layout-instability/#dom-layoutshift-hadrecentinput), поэтому их можно исключить из вычислений.

{% Aside 'caution' %} Флаг `hadRecentInput` становится true только для дискретных событий ввода, таких как касание, щелчок или нажатие клавиши. Непрерывные взаимодействия, прокрутка, перетаскивание или жесты сжатия и масштабированияне считаются «недавним вводом». Дополнительные сведения см. в [«Спецификации нестабильности макета»](https://github.com/WICG/layout-instability#recent-input-exclusion). {% endAside %}

#### Анимации и переходы

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

CSS-свойство [`transform`](https://developer.mozilla.org/docs/Web/CSS/transform) позволяет анимировать элементы, не вызывая смещений макета:

- Вместо изменения свойств высоты `height` и ширины `width` можно использовать `transform: scale()`.
- Чтобы перемещать элементы, следует избегать изменения свойств `top`, `right`, `bottom` или `left` и использовать вместо них `transform: translate()`.

## Как измерить CLS

CLS можно измерить в [лабораторных](/user-centric-performance-metrics/#in-the-lab) или [полевых условиях](/user-centric-performance-metrics/#in-the-field) с помощью следующих инструментов:

{% Aside 'caution' %} Инструменты для измерения в лабораторных условиях обычно загружают страницы в имитируемой среде и поэтому могут измерять только смещения макета, которые происходят во время загрузки страницы. В результате значения CLS, сообщаемые лабораторными инструментами для данной страницы, могут быть меньше, чем у реальных пользователей в полевых условиях. {% endAside %}.

### Инструменты для измерения в полевых условиях

- Отчет [Chrome User Experience Report](https://developer.chrome.com/docs/crux/)
- [PageSpeed Insights](https://pagespeed.web.dev/)
- [Search Console (отчет Core Web Vitals report)](https://support.google.com/webmasters/answer/9205520)
- [JavaScript-библиотека `web-vitals`](https://github.com/GoogleChrome/web-vitals)

### Инструменты для измерения в лабораторных условиях

- [Chrome DevTools](https://developer.chrome.com/docs/devtools/)
- [Lighthouse](https://developer.chrome.com/docs/lighthouse/overview/)
- [PageSpeed Insights](https://pagespeed.web.dev/)
- [WebPageTest](https://webpagetest.org/)

### Измерение CLS в JavaScript

{% BrowserCompat 'api.LayoutShift' %}

Чтобы измерить CLS с помощью JavaScript, можно воспользоваться [Layout Instability API (API нестабильности макета)](https://github.com/WICG/layout-instability). В следующем примере показано, как создать [`PerformanceObserver`](https://developer.mozilla.org/docs/Web/API/PerformanceObserver) который прослушивает неожиданные записи смещений макета `layout-shift`, группирует их в сеансы и регистрирует максимальное значение сеанса при каждом его изменении.

```js
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});
```

{% Aside 'warning' %}

Этот код показывает основной способ вычисления и регистрации CLS. Однако точно измерить CLS таким образом, чтобы метрика соответствовала измерениям в [отчете Chrome User Experience Report](https://developer.chrome.com/docs/crux/) (CrUX), довольно сложно. Подробности см. ниже:

{% endAside %}

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

Далее приведем различия между тем, что сообщает API, и тем, как рассчитывается метрика.

#### Различия между метрикой и API

- Если страница загружается в фоновом режиме или если она находилась в фоновом режиме до того, как браузер отрисовал любой контент, она не должна сообщать значение CLS.
- Если страница восстанавливается функцией [back/forward cache](/bfcache/#impact-on-core-web-vitals), значение CLS данной страницы должно быть сброшено до нуля, поскольку пользователи воспринимают такие посещения страниц как отдельные.
- API не сообщает о записях `layout-shift` для смещений внутри iframe, но для правильного измерения CLS их нужно учитывать. Подфреймы могут использовать API, чтобы сообщать о своих записях `layout-shift` в родительский фрейм для [агрегирования](https://github.com/WICG/layout-instability#cumulative-scores).

Более того, CLS трудно определять из-за того, что показатель измеряется на протяжении всей продолжительности жизни страницы:

- Пользователи могут держать вкладку открытой в течение *очень* долгого временидней, недель, месяцев. Фактически пользователь может никогда не закрыть вкладку.
- В мобильных операционных системах браузеры обычно не запускают обратные вызовы выгрузки страницы для фоновых вкладок, что затрудняет сообщение «окончательного» значения.

Чтобы справиться с такими случаями, CLS следует сообщать каждый раз, когда страница находится в фоновом режиме, в дополнение к любому времени, когда она выгружается ([событие `visibilitychange`](https://developer.chrome.com/blog/page-lifecycle-api/#event-visibilitychange) охватывает оба этих сценария). Системы аналитики, получающие эти данные, должны будут затем вычислить окончательное значение CLS на бэкенде.

Вместо того чтобы запоминать все эти тонкости, разработчики могут использовать для измерения CLS [JavaScript-библиотеку `web-vitals`](https://github.com/GoogleChrome/web-vitals), которая учитывает вышеупомянутые моменты:

```js
import {onCLS} from 'web-vitals';

// Measure and log CLS in all situations
// where it needs to be reported.
onCLS(console.log);
```

Полный пример измерения CLS в JavaScript приводится в [исходном коде `onCLS()`](https://github.com/GoogleChrome/web-vitals/blob/main/src/onCLS.ts).

{% Aside %} В некоторых случаях (например, в iframe с перекрестным происхождением) невозможно измерить CLS в JavaScript. См. подробности в разделе [«Ограничения»](https://github.com/GoogleChrome/web-vitals#limitations) библиотеки `web-vitals`. {% endAside %}

## Как улучшить показатель CLS

Для большинства веб-сайтов можно избежать всех неожиданных смещений макета, придерживаясь нескольких основополагающих принципов:

- **Всегда следует включать атрибуты размеров для изображений и видеоэлементов или иным образом резервировать необходимое пространство с помощью, например, [CSS-свойства aspect-ratio](https://css-tricks.com/aspect-ratio-boxes/).** Такой подход гарантирует, что браузер сможет выделить правильный объем места в документе во время загрузки изображения. Кроме того, можно также применять [политику функции unized-media,](https://github.com/w3c/webappsec-feature-policy/blob/master/policies/unsized-media.md) чтобы принудительно использовать это поведение в браузерах, поддерживающих политики функций.
- **Никогда не следует вставлять контент поверх существующего, кроме случаев, когда это требуется в ответ на взаимодействие с пользователем.** Это гарантирует, что любые изменения макета будут ожидаемыми.
- **Лучше выбирать анимацию трансформации, чем анимацию изменения свойств, запускающих изменение макета.** Нужно анимировать переходы таким образом, чтобы обеспечить контекст и непрерывность от состояния к состоянию.

Подробные сведения о том, как улучшить CLS, см. в статьях [«Оптимизация CLS»](/optimize-cls/) и [«Отладка смещений макета»](/debug-layout-shifts).

## Дополнительные ресурсы

- Руководство Google Publisher Tag по [минимизации смещения макета](https://developers.google.com/doubleclick-gpt/guides/minimize-layout-shift)
- [Понимание совокупного смещения макета](https://youtu.be/zIJuY-JCjqw), [Энни Салливан](https://anniesullie.com/) и [Стив Кобес,](https://kobes.ca/) [#PerfMatters](https://perfmattersconf.com/) (2020)

{% include 'content/metrics/metrics-changelog.njk' %}
