Зачем оптимизировать CLS
CLS (Cumulative Layout Shift) измеряет визуальную стабильность страницы: насколько сильно элементы сдвигаются во время загрузки и взаимодействия. Вы наверняка сталкивались с ситуацией — начинаете читать текст, а он внезапно прыгает вниз из-за подгрузившегося баннера. Или нажимаете кнопку «Отправить», а вместо неё попадаете на рекламу, потому что контент сместился. Именно это и измеряет CLS.
Google считает CLS одним из трёх Core Web Vitals. Целевое значение — меньше 0,1. Всё, что выше 0,25, — критическая проблема. По моей практике, CLS — самая коварная метрика: сайт может загружаться быстро, но раздражать пользователей постоянными скачками контента. И это бьёт по поведенческим факторам сильнее, чем медленная загрузка: пользователь не просто ждёт, а получает негативный опыт в момент взаимодействия.
Целевые значения CLS
- Хорошо: до 0,1 — страница визуально стабильна, элементы не прыгают
- Требует улучшения: 0,1-0,25 — заметные сдвиги, нужна оптимизация
- Плохо: больше 0,25 — критические сдвиги, прямое влияние на ранжирование и конверсию
Пошаговая инструкция: как найти и устранить сдвиги
Шаг 1. Измерьте текущий CLS
Откройте PageSpeed Insights и проверьте страницу. В секции полевых данных найдите значение CLS — именно эти данные учитывает Google для ранжирования. Затем посмотрите лабораторные данные Lighthouse: в секции «Avoid large layout shifts» перечислены конкретные элементы, вызывающие сдвиги.
Важный нюанс: CLS считается не только при загрузке страницы, но и за весь период взаимодействия — включая скролл и клики. Сдвиг от lazy-loaded изображения на третьем экране, от появления cookie-баннера после скролла, от раскрытия аккордеона — всё это учитывается.
Шаг 2. Найдите проблемные элементы через Chrome DevTools
Откройте DevTools (F12), перейдите на вкладку Performance и запишите загрузку страницы с включённой галочкой «Screenshots». После записи найдите события Layout Shift на таймлайне — они отмечены красными маркерами. Кликните на каждый сдвиг: внизу увидите, какой именно элемент сместился, из какой позиции в какую и на сколько это повлияло на общий CLS.
Для отлова сдвигов при взаимодействии (не при загрузке) используйте расширение Web Vitals для Chrome. Включите режим Console Logging в настройках расширения. В консоли DevTools будут появляться подробные записи о каждом Layout Shift с указанием элемента и его вклада в общий CLS. Это особенно полезно для поиска сдвигов, которые происходят при скролле или клике.
Шаг 3. Проверьте данные в Search Console
Откройте отчёт «Основные интернет-показатели» в Google Search Console. Страницы с плохим CLS группируются по шаблону — если проблема в конкретном типе страниц (например, все статьи блога), можно исправить шаблон один раз и решить проблему для всех затронутых URL.
Шаг 4. Устраните причины сдвигов
После того как проблемные элементы определены, переходите к исправлению. Ниже разберу самые частые причины CLS и конкретные CSS-решения для каждой.
Основные причины CLS и способы исправления
Изображения и видео без указанных размеров
Это причина номер один для большинства сайтов. Когда браузер загружает изображение без атрибутов width и height, он не знает заранее, сколько места выделить. Сначала отрисовывает контент вокруг, а когда картинка загрузится — раздвигает макет, и всё ниже прыгает вниз.
Решение 1: атрибуты width и height в HTML. Всегда указывайте размеры в теге img:
<img src="photo.webp" width="800" height="450" alt="Описание">
Браузер использует эти значения для расчёта aspect ratio и резервирует место до загрузки изображения. Для адаптивного дизайна добавьте в CSS:
img {
max-width: 100%;
height: auto;
}
Изображение будет уменьшаться пропорционально в контейнере, но место под него будет зарезервировано заранее.
Решение 2: CSS-свойство aspect-ratio. Современный и более гибкий способ:
.hero-image {
aspect-ratio: 16 / 9;
width: 100%;
object-fit: cover;
}
.product-thumb {
aspect-ratio: 1 / 1;
width: 100%;
object-fit: contain;
}
Свойство aspect-ratio резервирует место нужной пропорции до загрузки изображения. Не нужно знать точные размеры в пикселях — достаточно пропорции. Работает во всех современных браузерах.
Для видео и iframe (YouTube, Vimeo) проблема аналогичная. Обёртка с aspect-ratio решает её:
.video-wrapper {
aspect-ratio: 16 / 9;
width: 100%;
}
.video-wrapper iframe {
width: 100%;
height: 100%;
}
Динамически подгружаемый контент
Рекламные блоки, баннеры, виджеты отзывов, формы подписки, блоки «Похожие товары» — всё, что загружается после основного контента и вставляется в поток документа, вызывает сдвиги. Решение: зарезервируйте фиксированное пространство.
/* Контейнер для рекламного блока 300x250 */
.ad-slot-sidebar {
min-height: 250px;
min-width: 300px;
background-color: #f5f5f5; /* Плейсхолдер, чтобы не было пустоты */
}
/* Контейнер для баннера на всю ширину */
.banner-slot {
min-height: 90px;
width: 100%;
}
/* Контейнер для виджета отзывов */
.reviews-widget {
min-height: 400px;
}
Ключевое правило: если вы знаете размер динамического блока — задайте min-height контейнера. Контент ниже не будет прыгать при загрузке рекламы или виджета. Если точный размер неизвестен — задайте приблизительный min-height. Небольшое пустое пространство лучше, чем скачок контента.
Веб-шрифты и FOUT/FOIT
При загрузке кастомного шрифта текст сначала отображается системным шрифтом (или не отображается вообще), а затем перерисовывается. Метрики букв у системного и кастомного шрифта отличаются — текст меняет высоту, строки переносятся иначе, и всё ниже сдвигается. Это называется FOUT (Flash of Unstyled Text) и генерирует CLS.
Решение: font-display: swap с настроенным fallback.
@font-face {
font-family: 'MyFont';
src: url('myfont.woff2') format('woff2');
font-display: swap;
}
/* Подобранный fallback с корректировкой метрик */
@font-face {
font-family: 'MyFont Fallback';
src: local('Arial');
size-adjust: 105%;
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
}
Свойства size-adjust, ascent-override, descent-override и line-gap-override позволяют подогнать метрики fallback-шрифта под кастомный. Если системный шрифт рендерится с теми же метриками, что и кастомный — при переключении текст не прыгает, и CLS остаётся нулевым.
Для подбора точных значений используйте инструмент Fallback Font Generator — загрузите свой шрифт, и инструмент рассчитает коэффициенты для ближайшего системного аналога.
Cookie-баннеры и верхние уведомления
Плашки с согласием на cookies, верхние баннеры с акциями, уведомления о режиме работы — если они появляются после загрузки и сдвигают контент вниз, CLS растёт. Это одна из самых частых проблем.
Решение: позиционирование вне потока документа.
/* Cookie-баннер внизу экрана — не сдвигает контент */
.cookie-banner {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
}
/* Верхний промо-баннер — sticky, не сдвигает */
.promo-bar {
position: sticky;
top: 0;
z-index: 999;
}
/* Или зарезервируйте место заранее */
.notification-slot {
min-height: 48px; /* Высота уведомления */
}
Элементы с position: fixed и position: sticky не участвуют в потоке документа и не вызывают Layout Shift. Если же баннер должен быть частью потока — зарезервируйте под него место заранее через min-height, даже если он ещё не загрузился.
Табы, аккордеоны и раскрывающийся контент
Если при клике на таб или аккордеон контент меняет высоту — весь контент ниже сдвигается. Это считается Layout Shift, даже если сдвиг инициирован действием пользователя (в текущей версии алгоритма Google учитывает и такие сдвиги, если они происходят не в пределах 500 мс после взаимодействия).
Решение для табов:
/* Задайте фиксированную минимальную высоту */
.tab-content {
min-height: 300px; /* Высота самого высокого таба */
}
/* Или используйте CSS Grid с фиксированной областью */
.tabs-container {
display: grid;
grid-template-rows: auto 1fr;
}
.tab-panel {
grid-row: 2;
grid-column: 1;
min-height: 300px;
}
Решение для аккордеонов:
/* Анимация через max-height вместо height: auto */
.accordion-body {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
}
.accordion-item.active .accordion-body {
max-height: 500px; /* Заведомо больше реального контента */
}
Ещё лучше: используйте CSS-свойство transform для анимации вместо изменения height или max-height. Трансформации не вызывают Layout Shift, потому что выполняются на уровне GPU и не влияют на поток документа.
Динамическая вставка элементов через JavaScript
Любой элемент, вставленный в DOM через JavaScript выше текущего видимого контента, вызывает сдвиг. Особенно часто это происходит с:
- Баннерами, которые загружаются через рекламную сеть
- Виджетами чатов, которые раздвигают макет при инициализации
- Блоками «Недавно просмотренные», которые подгружаются через AJAX
- Уведомлениями и toast-сообщениями
Правило: каждый динамически вставленный блок должен иметь placeholder с фиксированными размерами. Если размер заранее неизвестен — вставляйте контент ниже текущего viewport (ниже видимой области экрана) или используйте position: fixed/absolute, чтобы не влиять на поток документа.
Встроенные карты и iframe
Яндекс Карты, Google Maps, встроенные формы — все эти iframe загружаются медленно и раздвигают макет, если под них не зарезервировано место.
.map-container {
aspect-ratio: 16 / 9;
width: 100%;
background-color: #e8e8e8;
}
.map-container iframe {
width: 100%;
height: 100%;
border: 0;
}
Серый фон background-color показывает пользователю, что здесь будет контент, и предотвращает ощущение «пустоты» до загрузки карты.
Типичные ошибки
- Тестировать CLS только при загрузке страницы. Google учитывает CLS за весь период взаимодействия — включая скролл, клики и любые действия пользователя. Сдвиг от lazy-loaded изображения на пятом экране тоже считается. Тестируйте страницу полностью: прокрутите до конца, покликайте по табам и аккордеонам.
- Прятать проблему через overflow: hidden. Скрытие переполнения на body не решает проблему CLS — браузер всё равно фиксирует сдвиг элементов внутри видимой области. Нужно устранять причину, а не маскировать следствие.
- Забывать про мобильную версию. На мобильных CLS часто хуже: экран меньше, и сдвиг на 50 пикселей визуально заметнее, чем на десктопе. Кроме того, на мобильных чаще появляются динамические элементы (sticky-хедер, промо-баннер), которые сдвигают контент. Тестируйте обязательно в мобильном режиме.
- Добавлять контент через JavaScript без резервирования места. Каждый динамически вставленный блок без placeholder — потенциальный источник CLS. Это относится к рекламе, виджетам, отложенным блокам.
- Игнорировать шрифты как источник CLS. Переключение с системного шрифта на кастомный — частая и незаметная причина сдвигов. Без корректировки метрик fallback-шрифта текст меняет высоту при загрузке шрифта, и весь контент ниже прыгает.
- Не задавать размеры для img внутри CMS-контента. Если редактор WordPress вставляет изображения через WYSIWYG без width/height — каждая картинка в статье будет источником CLS. Убедитесь, что тема или плагин автоматически добавляют атрибуты размеров.
Что проверить в итоге
- CLS в полевых данных PageSpeed Insights ниже 0,1 для мобильных и десктопных устройств.
- В Search Console нет страниц со статусом «Плохо» по CLS.
- Все изображения и видео имеют явно указанные width и height (или CSS aspect-ratio).
- Рекламные блоки и динамический контент обёрнуты в контейнеры с min-height.
- Шрифты используют font-display: swap с настроенным fallback (size-adjust, ascent-override).
- Cookie-баннер и верхние уведомления не сдвигают контент (position: fixed или sticky).
- При клике на табы и аккордеоны контент ниже не прыгает (задан min-height или используются трансформации).
- Встроенные карты и iframe обёрнуты в контейнеры с aspect-ratio.
- В Chrome DevTools (Performance → Layout Shifts) нет красных маркеров с высоким вкладом в CLS.
- Мобильная версия проверена отдельно и CLS на ней не превышает 0,1.