awesome-everything EN
↑ Обратно к восхождению

Браузер и фронтенд-рантайм

CLS: почему происходят сдвиги лейаута и как их остановить

Суть Cumulative Layout Shift оценивает худший всплеск неожиданного движения — картинки без размеров, поздняя реклама, смены шрифтов, динамически инжектируемые баннеры — и фикс всегда один: зарезервировать место до прихода байтов.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 12 min

Статья загружается быстро. Пользователь начинает читать второй абзац. Затем рекламный блок над статьёй заполняется и сдвигает всё вниз на 300 пикселей. Пользователь был на полуслове; теперь он на середине другого абзаца. Он ничего не тапал. Страница сдвинулась сама. Это CLS — и он проще всего предотвращается из трёх vital, если понять одно правило, стоящее за всеми четырьмя классическими причинами.

Как считается оценка.

Каждый раз, когда видимый элемент меняет позицию между двумя кадрами без причины, инициированной пользователем, браузер записывает layout shift. Оценка одного сдвига:

impact fraction × distance fraction

Impact fraction: совокупная площадь всех сдвинувшихся элементов как доля вьюпорта. Если картинка, покрывающая половину вьюпорта, прыгает — impact fraction 0.5. Distance fraction: наибольшее расстояние, на которое сдвинулся любой элемент, как доля высоты вьюпорта. Сдвиг на 20% высоты вьюпорта даёт 0.2.

Таким образом, один сдвиг, двигающий полувьюпортный элемент на 20% высоты вьюпорта, оценивается 0.5 × 0.2 = 0.1 — ровно на «хорошей» границе.

CLS — не пожизненная сумма всех сдвигов. Это сумма сдвигов в худшем session window: кластер сдвигов, где каждый в пределах 1 секунды от предыдущего, и весь кластер охватывает не более 5 секунд.

Правила подсчёта CLS
Оценка сдвига
impact fraction × distance fraction
CLS
Сумма худшего 5-с session window
Окно исключения сдвига после ввода
500 мс
Хороший порог (p75)
≤0.1

Окно в 500 мс исключения — что CLS не наказывает.

Сдвиги в пределах 500 мс от взаимодействия пользователя исключаются. Открытие аккордеона, раскрытие выпадающего меню, клик «показать ещё» — результирующее движение ожидается пользователем и не засчитывается в CLS. CLS наказывает только движение, которое пользователь не вызвал и не ожидал.

Четыре классические причины и их фиксы.

  1. Картинки без размеров. <img> без атрибутов width и height имеет нулевую высоту до прихода байтов. Когда картинка загружается, браузер узнаёт её размеры, перезапускает компоновку, и всё ниже прыгает вниз. Фикс: всегда задавать атрибуты width и height (современные браузеры автоматически выводят aspect-ratio из них и резервируют правильно пропорциональный блок). CSS может делать картинку fluid с width: 100%; height: auto — атрибуты дают пропорцию, CSS — адаптивный размер.

  2. Реклама, эмбеды и iframe, инжектируемые в незарезервированное место. Рекламный блок, рендерящий 250 пикселей контента в контейнер без зарезервированной высоты, сдвигает всё ниже. Фикс: оборачивать каждый рекламный и embed-слот в контейнер с min-height, равным наибольшему ожидаемому размеру.

  3. Reflow от веб-шрифтов. Фоллбэк-шрифт с другими метриками рендерится первым; когда веб-шрифт загружается, браузер перекомпоновывает текст — символы шире или уже, строки переносятся, элементы ниже сдвигаются. Фикс: использовать size-adjust и дескрипторы ascent-override/descent-override шрифта, чтобы метрики фоллбэка совпадали с веб-шрифтом; или font-display: optional для применения веб-шрифта только при повторных визитах.

  4. Динамически инжектируемый контент над существующим. Cookie-баннер, панель уведомлений или виджет чата, вставленный над телом страницы в runtime, сдвигает всё вниз. Фикс: вставить в заранее зарезервированное место (контейнер с известной высотой в лейауте), или рендерить как оверлей (fixed/absolute), чтобы не участвовал в flow-лейауте вообще.

Объединяющий принцип: резервировать место для всего, чей конечный размер неизвестен во время парсинга, или убедиться, что оно никогда не участвует в flow-лейауте.

Ловушка с анимациями.

Анимирование layout-свойств — top, left, height, width, marginсоздаёт layout shifts и может навредить CLS, даже если движение выглядит преднамеренным для разработчика. Фикс — анимировать transform вместо (translateY, scale), который композитируется и никогда не запускает компоновку. Сдвиг, вызванный CSS-анимацией, всё равно является сдвигом, если он не следует за взаимодействием пользователя в пределах 500 мс.

Почему это работает

Модель session window заменила оригинальную «пожизненную сумму», потому что долго живущие страницы и infinite scroll несправедливо наказывались за сдвиги, происходившие через пять минут после загрузки — далеко за пределами контекста чтения пользователя. Session window фокусирует CLS на всплесках плохого поведения: кластер сдвигов при перезагрузке рекламы или пакет картинок без размеров загружается, а не накопленная стоимость сайта, которым кто-то пользуется час. CLS теперь более репрезентативен для того, что пользователь реально замечает.

Зарезервировать место для поздно загружаемой картинки и устранить CLS

1/3
Викторина

Пользователь открывает аккордеон, и контент под ним сдвигается вниз. Засчитывается ли это в CLS?

Викторина

Cookie-баннер инжектируется над телом статьи после загрузки страницы, сдвигая весь контент вниз на 60 пикселей. Какой правильный CLS-фикс?

Вспомните перед уходом
  1. 01
    Как считается оценка одного layout shift и что такое CLS session window?
  2. 02
    Назовите четыре классические причины CLS и фикс для каждой.
  3. 03
    CSS-анимация двигает элемент через 'top' и страница не проходит CLS-аудит. Как починить, не убирая анимацию?
Итог

CLS оценивает худший всплеск неожиданного движения лейаута: оценка одного сдвига — impact fraction × distance fraction, и CLS репортит худший 5-секундный session window. Сдвиги в пределах 500 мс от взаимодействия пользователя исключаются — CLS наказывает только движение, которое пользователь не вызвал. Четыре классические причины: картинки без размеров, реклама и эмбеды в контейнерах без зарезервированной высоты, reflow от веб-шрифта при несовпадении метрик фоллбэка, и динамически инжектируемый контент над существующим flow. Фикс для всех четырёх следует одному правилу: всё, чей конечный размер неизвестен во время парсинга, должно иметь зарезервированное место, или использовать overlay-позиционирование вне flow-лейаута. Анимирование layout-свойств (top, left, height) генерирует сдвиги — анимируйте transform вместо.

Связанные уроки
встречается в143
Продолжить восхождение ↑Lab vs field: почему они расходятся и как использовать каждый
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.