Оптимизация CLS (совокупное смещение макета)

17.01.2023

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

К примеру вы открываете страницу сайта, начинаете читать текст на ней и вдруг неожиданно вас перекидывает на другую часть страницы. А все из-за того что в этот момент на страницу подгрузилась большая картинка чуть выше и она “вытолкнула” ниже тот видимый контент, который вы только что просматривали. И весь контент сместился вниз на экране (или вбок). В итоге вам приходится прокручивать страницу и искать то место до куда вы уже дочитали.

Аналогичный пример - открываете страницу и нажимаете на какую-то кнопку/ссылку на странице (добавить в корзину, поиск и т.п.). Но оказывается, что страница еще не до конца загрузилась, и именно в момент когда вы кликнули по ссылке - страница сместилась и ваш клик пришелся уже на соседний объект.

Такие ситуации возникают достаточно часто на разных сайтах, и они очень раздражают посетителя. А раздражженный посетитель вашего сайта уже с меньшей вероятностью дочитает страницу или что-то у вас купит. Именно поэтому показатель CLS рекомендуется оптимизировать на всех страницах сайта.

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

Самые распространенные ошибки, которые приводят к плохим показателям CLS:

  • Картинки, у которых в html-разметке не указаны размеры.
  • Рекламные блоки, видео, карты и фреймы, у которых в разметке не указаны размеры.
  • Блоки, подгружаемые динамически (генерируется через javascript или подгружается через ajax)
  • Использование шрифтов, с эффектом FOIT или FOUT в процессе загруззки

Чтобы исправить эти ошибки ниже дан ряд рекомендаций.

Изображения

Указывайте размеры картинок, видео и фреймов в разметке.

Всегда указывайте размеры прямо в html-коде, не полагайтесь css или автоматичесий расчет размеров браузером.

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

Изначально когда веб-разработка только зарождалась, было необходимым заполнять атрибуты width и height у тегов <img>. Именно это и позволяло браузерам зарезервировать необходимое пространство на странице, куда потом выводится загруженная картинка. Это позволяло свести к минимуму смещения макета во время загрузки страниц.

<img src="photo.jpg" width="640" height="360" alt="Фотография товара" />

При этом обратите внимание, в этом примере ширина и высота задается без указания единиц измерения. Размеры картинки на странице обеспечиваются за счет резервирования браузером области 640x360 px. Чтобы уместиться в эту область, картинка может растягиваться, если она недостаточно большая в исходных размерах.

И все было хорошо до появления технологий адаптивного дизайна сайтов. При использовании этого подхода разработчики перестали указывать свойства width и height в html-разметке страницы. А логика размера картинок задавалась уже в CSS-стилях страницы:

img {
    width: 100%; /* или max-width: 100%; */
    height: auto;
}

И вот тут начались проблемы, на которые долго никто не обращал внимания.

Дело в том что CSS-стили подгружается и применяются на странице конечно же позже чем браузера начинает рендерить страницу на основе ее html-разметки.

Получается, что место под картинку бразер может зарезервировать только после того, как картинка начнет загружаться браузером (или вычисляются стили этой картинки на основе css). Из-за этого страница сайта может перерисовываться браузером по мере подгрузки и появления всех изображений на экране. И частенько получается, что текст появлялся на экране внезапно или прыгает вниз-вверх в процессе рендеринга. И вот это и создает большие неудобна для посетителя и раздразжает его.

Для того чтобы зарезервировать место на странице под картинку, браузер использует следующие данные:

  • исходные размеры загружаемой картинки (ширина и высота)
  • отношение сторон загружаемой картинки

Допустим оригинальное изображение размером 900x300 px. Зная эти размеры, браузер понимает, что соотношение сторон - 3:1.

И на основе этого соотношения сторон браузер вычисляет сколько места на странице нужно отвести под эту картинку.

Если на основе css-стилей движок браузера понимает, что эту картинку надо вывести в браузер с высотой 150px, то зная соотношение сторон (3:1) браузер вычисляет размер блока 450x150 px, который нужно зарезервировать на странице (150 * ( 3 / 1))

Если же в соответствии с css ясно что ширина картинки должна быть 300px, то браузер вычисляет ее высоту - 100 px (Вычисляем как 300 * (1 / 3))

Более современный и правильный подход

Современные версии браузеров рассчитывают соотношения сторон у каждой картинки либо на основе атрибутов width/height в html-коде (если эта атрибуты заданы), либо на основе css-свойства aspect-ratio.

По-умолчанию aspect-ratio в CSS вычисляется также на основе атрибутов width/height:

img {
    aspect-ratio: attr(width) / attr(height);
}

Примеры:

<!-- устанавливаем соотношение 6:9 за счет указания width/height (640:360 = 6:9) -->
<img src="test.jpg" width="640" height="360" />
<!-- устанавливаем соотношение через css -->
<img src="test.jpg" id="aspect-ratio-6-9" />
<style>
img.aspect-ratio-6-9 {
    aspect-ratio: 6 / 9;
}
</style>

За счет этого браузеру не нужно дожидаться загрузки изображения.

Достаточно прочитать css и рассчитать хотя бы размер одной из сторон картинки (допустим, width: 100%),, в которых она должна быть отрендерена согласно css-стилям. И далее вычисляется вторая сторона на основе aspect-ratio - он также определяется css-стилями (либо явно заданным aspect-ratio для текущего элемента, либо на основе базового aspect-ratio, вычисленного в css за счет атрибутов width/height в html-разметке)

А вот пример как задать размеры картинки через css, на основе размера родительского блока, в котором находится наша картинка:

img {
    height: auto;
    width: 100%;
}

В результате по ширине картинка будет занимать все содержимое родительского блока. А высота картинки будет рассчитана автоматом на основе aspect-ratio.

Как это будет работать при использовании srcse.t

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

<img width="1000" height="1000"
 src="test.jpg"
 srcset="test-1000.jpg 1000w, test-2000.jpg 2000w, test-3000.jpg 3000w"
 alt="Тестовая картинка c srcset"
/>

Обратите внимание: если вы указываете атрибуты width/height, то у всех изображений, указанных в srcset, вам нужно соблюдать одинаковое соотношение стороне.

Другой вариант адаптивной верстки изображений - использование связки элементов <picture> и <source> вместо <img> и srcset.

<picture>
    <source media="(max-width: 799px)" srcset="test-480w-cropped.jpg" />
    <source media="(min-width: 800px)" srcset="test-800w.jpg" />
    <img src="puppy-800w.jpg" />
</picture>

В этом случае браузер выберет наиболее подходящий вариант изображения основываясь на атрибуте media.

Отношение сторону у разных версий изображений внутри может быть разный. Пока браузеры по-разному могут обрабатывать такую разметку. В том числе перекомпоновка (повторный рендеринг) страницы возможен. А значит это может приводить и к смещениям макета во время загрузки страницы.

Рекламные объявления

Различные рекламные сети (Яндекс, Google и прочие) внедряются на страницы с помощью javascript-кода. А значит само добавление рекламных блоков на страницу происходит параллельно основной отрисовки страницы. Мало того, многие сети поддерживают динамические размеры объявлений, подстраиваемые не только под размер экрана, но и под родительский блок на странице, в котором выводится реклама. С точки зрения эффективности рекламы - это конечно хорошо.

А вот с точки зрения оптимальной загрузки страницы - это существенный минус.

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

Наиболее часто распространенные случаи, когда рекламный блок приводит к смещению макета:

  • в момент, когда javascript рекламной сети добавляет свой рекламный контейнер в DOM-структуру страницы;
  • в момент, когда сам сайт меняет размер рекламного блока на основе собственного javascript- или css-кода;
  • в момент, подгрузки библиотеки маркетинговых тегов (меняется размер рекламного блока);
  • в момент, когда конкретное объявление добавляется и отрисовывается в рекламный блок (ведь рекламный блок-контейнер и рекламное объявление могут иметь разные размеры).

Как можно уменьшить сдвиг макета в этом случае?

Можно заранее зарезервировать место под рекламный блок с помощью CSS-стилей.

Рекомендуем протестировать это на разных наиболее популярных размеров экранов, ведь на разных экранах придется резервировать разные размеры блока.

Также нужно предусмотреть случай, когда вы зарезервировали место, но объявлений в блоке так и не появилось (нет рекламодателей под ваши условия в данным момент и прочее) - лучше не скрывать и не сворачивать этот пустой блок, а чем-то его заполнить.

Также стоит мониторить историю/статистику, чтобы понять какие наиболее частые размеры рекламных блоков использует рекламная сеть на вашем сайте. И подправить зарезервированные размеры с этим учетом.

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

Если есть какие-то редко используемые рекламные блоки (которые зачастую пустые) - то лучше их изначально свернуть/скрыть. И разворачивать только если рекламное объявление все-таки там появилось.

Избегайте вывода рекламного блока на первом экране (если оно не прилипшее), который видит посетитель сразу после загрузки страницы. Чем ниже размещено объявление на страницы, тем меньше под ним размещено элементов (соответственно и смещаться будет меньший объем элементов страницы).

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

Фреймы, видео, карты и прочие встраиваемые объекты

Постарайтесь взглянуть на эти объекты как-будто это рекламные блоки.

И примените к ним соответствующие методики и рекомендации.

Основная идея всё та же - старайтесь максимально точно заранее резервировать место под эти блоки с помощью CSS.

Блоки, добавляемые на страницу динамически (генерируется через javascript или подгружается через ajax)

Зачастую смещение макета создается из-за различных элементов интерфейса страницы, которые появляются в области просмотра страницы:

  • Блок подписки на новости.
  • Похожие статьи/товары, связанные материалы.
  • Блок «Используйте приложение под Android/iOS».
  • Блок «Расписание работы в праздники».
  • Уведомление об обработке персональных данных.
  • Прочие блоки, добавляемые динамически уже после формирования страницы.

Лучше максимально избегать использование таких техник.

Или хотя бы резервируйте место под этим блоки (как и в случае рекламных блоков).

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

Другая техника - дождаться от посетителя какого-то действия, после которого он не будет удивлен добавлению/изменению блока на странице (к примеру, кнопка “показать еще”).

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

Нестандартные веб-шрифты

При использовании нестандартных шрифтов на сайте (которые не встроены в браузер и ОС, а подгружаются на страницу по URL) в процессе рендеринга страницы может возникать смещение макета. Это называется FOUT-миганием и FOIT-миганием.

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

FOIT-мигани возникает в этот же момент, но в том случае, если никакой резервный шрифт не использовался до момента окончательной загрузки нестандартного шрифта. Соответственно до этого момента текст выводится невидимым (без какого-либо шрифта). И замена “невидимого шрифта” на только что загруженный шрифт и возникает мигание данного типа.

Уменьшить вероятность смещения макета из-за шрифтов можно следующими техниками:

  • Использовать CSS-свойство font-display.
  • Использовать API загрузки шрифтов.
?