Как уменьшить Cumulative Layout Shift (CLS) на сайте
Приветствую, друзья. Сегодня разберем еще один элемент оптимизации сайта под требования Google Page Speed Insights, обозначенный как смещение макета страницы в процессе загрузки, в результатах проверки фигурирует под названием Cumulative Layout Shift (CLS).

Если говорить простым языком, то уровень Cumulative Layout Shift показывает, насколько сильно прыгают разные блоки на сайте, в течение загрузки веб-страницы.
Содержание:
Причиной таких попрыгушек являются элементы с неопределенными или неверно заданными параметрами ширины и высоты. Например, картинка вставлена без размеров и в начале загрузки показывается как узкая область, обтекаемая текстом, а после загрузки изображение увеличивается до заданного размера, и весь текст уходит вниз.
Часто аналогичное влияние могут давать блоки рекламы, формы и т.д.
Для исправления необходимо обладать базовыми знаниями в CSS и уверенно пользоваться браузером.
Какой вред несет смещение макета
Чтобы долго не объяснять, приведу картинку, где показано, как из-за CLS смещения пользователь ошибочно нажимает на рекламу вместо кнопки социальной сети, так как блок рекламы загрузился позднее и сдвинул контент в момент клика.

Так как такие ошибки вредны людям, негативно к ним относятся и поисковые системы. Сайты, имеющие подобные ошибки, могут понижаться в поисковой выдаче.
Параметр Cumulative Layout Shift лежит в диапазоне от 0 до 1.
- 0 – это очень хорошо, означает, что все размеры и расположение элементов страницы задаются при первичном обращении браузера к сайту, а в процессе загрузки только заполняются данными.
- 1 – это очень плохо, означает, что первый отрисованный экран не имеет ничего общего с конечным видом сайта.
На значение влияет доля контента, который сдвигается, и расстояния, на которое происходит смещение.
Нормальным совокупным смещением макета считается значение <0,1, CLS в диапазоне 0,1-0,25 приемлемы, но Google уже обратит внимание на проблему, а цифры выше 0,25 – явная ошибка.

К сожалению, сам Page Speed Insights не указывает конкретные элементы, оказывающие негативный эффект.
Встает вопрос, как оптимизировать Cumulative Layout Shift, если неизвестны проблемные места.
На помощь придет DevTools – утилита уже встроенная в Яндекс Браузер и Chrome.
Я покажу весь процесс на примере собственного сайта, на котором как раз оказались страницы с проблемами.
Работа проводится в 2 этапа:
- Поиск элементов с неправильными свойствами;
- Правка их CSS стилей.
Поиск элементов, смещающих макет
Чуть не забыл, найти страницы с высоким значением CLS, если они вам заранее неизвестны можно через Google Search Console (инструкция, как туда добавить сайт).
В консоли нужна вкладка «Основные интернет показатели», там пункт «Мобильный». Внизу страницы приведен список замечаний, требующих устранения, среди них находится CLS.

Клик по строке откроет список страниц.
Открываем страницу, требующую улучшения, в браузере и нажимаем одновременно кнопки Ctrl+Shift+I. Откроется инструмент веб-разработчиков. Второй вариант его запустить – кликнуть правой кнопкой мыши в любом месте сайта и в меню выбрать «Исследовать элемент».


В панели переводим сайт в мобильный режим и открываем вкладку «Perfomanse».

Теперь перезапускаем страницу в отладочном режиме, нажав на Ctrl+Shift+E.

Это действие приведет не только к обновлению страницы, но и к покадровой записи всего процесса загрузки страницы со сбором разной информации.
Отчет покажется очень сложным, но нам в нем нужна всего одна строка под названием «Experience».
Размер полей в отладочной панели DevTools меняется с помощью мыши, для этого зажимаем край блока и тянем в нужную сторону, так можно увеличить отчет до комфортного размера.
В этой строке отмечаются участки, на которых макет страницы меняется. Этапов смещения может быть несколько, кликните по каждому для подробностей.
Под диаграммой идет сводный список сдвигаемых элементов.

По метке в Related Node мы поможет понять, какая часть сайта меняется и негативно влияет на Cumulative Layout Shift. Осталось понять, что приводит к изменениям.
Переносим внимание на строку диаграммы, где размечены скриншоты и аккуратно переводим курсор мыши в том районе, где происходит смещение макета. Если изменения велики, то ответ будет виден невооруженным глазом.

Итак, то, что ломает наш макет обнаружено. В моем примере это картинка статьи, у которой неверно заданы начальные размеры. Стандартно, для десктопной версии, картинка имеет размер 300 на 170, а для маленьких экранов ширина меняется на 160, а высота была задана параметром auto. В результате браузер сначала подтягивал размер 170, а уже потом, когда картинка прогружалась полностью, пропорционально приводил высоту к ширине.
Исправление ошибок в макете
Чтобы исправить ошибку я открыл файл style.css. В блоке отвечающем за маленькие экраны (у меня это меньше 560 пикселей шириной) была добавлена строчка max-height: 100px.

Теперь в базовом макете высота изображения устанавливается на 100 px вместо 170 и после полной загрузки контента изменения практически не происходят. В DevTools совокупное смещение макета показывает 0,0163, а проверка Гугл снизила Cumulative Layout Shift до 0.

У сайтов, работающих на CMS, как правило, ошибки имеют повторяющийся характер и связаны с настройками шаблона, поэтому, исправление в одном месте скажется на всех страницах сайта или на определенной группе страниц. В моем примере одна строка в CSS файле WordPress шаблона уменьшила Cumulative Layout Shift до нуля одновременно на главной и на всех страницах категорий.
Иногда, повысить качество сайта проще, чем кажется. Если что-либо по теме CLS осталось непонятно или не получается так, как описано в инструкции, то задавайте вопросы, в комментариях, постараюсь помочь.
Статьи из этой же рубрики:
8 комментариев на «“Как уменьшить Cumulative Layout Shift (CLS) на сайте”»
Добрый день, подскажите, пожалуйста, на любом шаблоне можно поправить CLS? Я была бы рада, если бы Вы мне помогли с внесением правок на моем сайте, пожалуйста, свяжитесь со мной.
Здравствуйте. Сделать можно в любом, вопрос сложности самого шаблона — от этого зависят трудозатраты. Напишите мне через раздел «Мои услуги«.
Спасибо, спасли!
Долгие месяцы не могли подтянуть CLS на, но одна статья способна на многое))
Здравствуйте!
Пытаюсь оптимизировать CLS страниц каталога товаров в интернет-магазине, но пока как-то безуспешно.
К примеру, страница
bizoon.com.ua/dogs/dog-equipment/oshejniki/
Особенно критично это на мобильной версии. Основная проблема в блоках
Задавал значения ширины и высоты, но все как-то безуспешно.
У вас в стилях всё стоит auto или в %, укажите фиксированные параметры в px, как описано в этой статье.
Спасибо!
А если динамические размеры блока в зависимости от размера дисплея устройства, то указывается параметр max-width, max-height?
Да.
У вас при загрузке первый товарный блок сналача на всю ширину экрана разворачивается, а потом к нормальному — это огромный скачок — это при анализе видно невооруженным взглядом.
Оптимизация производительности фронтенда. Часть 2. Event loop, layout, paint, composite
Ночь. Стук в дверь. Открыть. Стоят двое. «Верите ли вы в Event loop, нашу главную браузерную цепочку?» Вздохнуть. Закрыть дверь. Лечь досыпать. До начала рабочего дня еще 4 часа. А там уже ивент лупы, лейауты и прочая радость…
В первой части мы говорили о первой загрузке и работе с ресурсами. Сегодня я расскажу о второй части оптимизации производительности фронтенда. О том, что происходит с нашей страницей, когда она загружена, на что уходит процессорное время и что с этим делать. Ключевые слова: event loop, paint \ repaint, layout \ reflow, composite.

Завязка. Вопросы для самопроверки
Если хочется сразу начать поглощать контент статьи, пропустите этот раздел
Event loop популярен не только в ежедневной работе, но и на собеседованиях. И пойди разберись, где он популярнее. Существует три вопроса, которые можно задать себе, чтобы проверить, понимаете ли вы этот браузерный механизм. Два из них — завсегдатаи собеседований, а вот третий показывает понимание event loop для своей практики. Они довольно простые, но будут расставлены по возрастанию сложности:
Будет ли выведено «1» в консоль? Почему?
Есть сайт, а на сайте ссылка, у которой при наведении cursor: pointer ставится через :hover стиль CSS и кнопка, у которой также по :hover меняется background-color c серого на синий. Добавляем скрипт:
Вопрос: Что будет если навести мышку на ссылку? А на кнопку? Почему?
Как анимировать выпадающий элемент по height с 0 до auto? Здесь важно обсудить способы c помощью JS и/или CSS. Кстати, если гуглить этот вопрос, то stackoverflow вначале предлагает неверный ответ. Суть понимания event loop и работы браузеров сводится к тому, как замерить то самое height = auto с помощью JS.
Наша цель
Прийти к достаточно глубокому пониманию вот этой схемы: 
Идти к ней будем постепенно, с подробными остановками на каждом из этапах
Event Loop
В старых операционных системах была похожая штука. Выглядела она условно вот так:
Такой код всегда забивал ЦПУ процессоров в 100%. Что и было в старых версиях windows. Сейчас планировщики операционных систем очень сложные. Там есть и приоритизация, и исполнение, и различные очереди.
Итак. Чтобы начать, нам понадобится какой-то бесконечный цикл, который будет опрашивать, есть ли у нас задачи. Например такой:

Теперь нам нужно откуда-то эти задачи получать.
Зададим себе вопрос: что является триггером, чтобы наш JS код начал выполняться?
Это может быть:
- Браузер загрузил тег <script>
- Отложенные задачи: setTimeout , setInterval , requestIdleCallback
- Ответ от сервера через XmlHttpRequest , fetch и т.п.
- События и вызовы подписчиков через браузерное API: click , mousedown , input , blur , visibilitychange , message и так далее, множество их. Часть из них вызывается пользователем (кликнул на кнопку, альт-табнулся и т.д.).
- Изменение состояния промисов. В некоторых случаях, это происходит за пределами нашего JS кода
- Обзерверы, такие как: DOMMutationObserver , IntersectionObserver
- RequestAnimationFrame
- Что-то еще? 🙂
Почти все эти вызовы планируются через WebAPI (его еще иногда называют браузерным API). То есть:
- Мы вызываем setTimeout(function a()<>, 100)
- WebAPI откладывает задачу на 100мс
- Через 100мс, WebAPI кладет function a() в очередь (TaskQueue)
- Event Loop на нужный виток подбирает задачу
Нашему JS коду нужно как-то взаимодействовать с DOM. Считывать размеры элементов, добавлять свойства, рисовать какие-нибудь выпадающие элементы. Делать интерфейс живым.
Это накладывает ограничения на отрисовку элементов. Мы не можем просто так взять и запустить 2 потока, чтобы в одном выполнялся JS, а в другом CSS и отрисовка. Это потребовало бы большого количества синхронизаций кода, либо могло бы привести к неконсистентому исполнению. Поэтому и JS, и расчет отображения элементов (расположения, цвета и т.д.) работают в одном потоке. Значит в нашем Event Loop кроме JS есть «отрисовка». Давайте поместим ее в отдельную очередь и назовем render queue:

Итак, у нас есть две точки входа. Одна для большинства JS операций, другая для отрисовки. Теперь нам нужно разобраться, что же такое SomeJSTasks и как оно работает.
Для исполнения большей части JS кода у нас есть 2 очереди:
- TaskQueue — здесь почти все ивенты, отложенные задачи и т.п. Задача из этой очереди Task
- MicroTaskQueue — здесь обработка промисов и MutationObserver. Из этой очереди: MicroTask
Обновление экрана
Event loop неразрывно связан с обновлением экрана. В нем исполняется не только JS код, но и рассчитываются кадры страницы.
Браузеры стараются отображать изменения на странице максимально быстро. Здесь существует ряд ограничений:
- Хардварные ограничения: частота обновления экрана
- Софтверные ограничения: настройки ОС, браузера, настройки энергосбережения и т.д.
Большинство современных устройств и настроек поддерживает 60FPS. Это число будет и у подавляющего количества браузеров. Поэтому далее в статье будем пользоваться 60FPS, но в голове держим, что это число может быть другим.
Для нашего Event Loop это означает, что на исполнение тасков отводится примерно 16.6мс (при 60FPS)
Что такое TaskQueue
Как только задачи набиваются в очередь Task, на каждом витке event loop мы достаем верхний таск и исполняем его. Если у нас остается достаточно времени, т.е. в render queue не появилось тасков, мы можем достать еще одни таск и т.д., пока не придет время для отрисовки нового кадра пользователю.
Разберем пару примеров:

У нас в очереди лежит 3 таска TaskA, TaskB, TaskC. Event Loop берет первый таск и исполняет. Он занимает 4 мс. Затем Event Loop проверяет другие очереди (Microtask queue и render queue) — они пустые. Поэтому EventLoop исполняет второй таск. Второй таск занимает еще 12 мс. Итого 16 мс. Браузер добавляет в Render queue таски на отрисовку нового кадра, Event Loop берет эти таски. Они занимают примерно 1 мс. После этого Event Loop переходит обратно исполнять таски из TaskQueue.
Event Loop не умеет предсказывать, сколько времени будет исполняться тот или иной таск. И он не умеет приостанавливать таск для того, чтобы отрендерить страницу пользователя, так как браузерный движок не может знать, можно ли отрисовать то, что изменил JS или это только подготовка элементов для отображения их пользователю. То есть: Пока исполняется синхронный JS код, изменения не будут отображены пользователю, но могут быть посчитаны.
Поэтому вот другой пример:

Здесь у нас только два таска в нашей очереди. Первый таск выполнялся 240 мс, так как 60FPS предполагает рендер кадра каждые 16.6 мс. А это означает, что мы пропустили примерно 14 кадров в эту секунду. Поэтому как только таск закончится, event loop начнет выполнять ожидающие таски из render queue, чтобы нарисовать кадр. Важный момент: то, что мы пропустили 14 кадров НЕ означает, что мы 15 раз будет рисовать кадры без остановки.
Прежде чем перейти к микротаскам, разберем такую штуку, как стек вызовов:
Стек вызовов
Стек вызовов — это список, который отражает, какие функции сейчас вызываются и куда будет совершен переход, после окончания исполнения текущей функции.
Мы запускаем этот код в консоли браузера и останавливаемся на дебаггере. Как будет выглядеть наш стек вызовов?
Мы начали наш стек из inline кода, значит самым верхом будет эта строчка. Как правило, в хром она будет указана просто ссылкой на нее. Обозначим ее inline . Дальше мы падаем в startDndGame , и вызываем несколько раз findAFriend . Однако он не попадет в итоговый callStack , потому что из всех findAFriend мы вышли. Итого наш стек вызовов будет выглядеть вот так:

Что же такое микротаски?
Микротаски ограничены. Это либо колбеки для promise, либо mutationObserver. Сама идея появления микротасок довольно костыльна, но она дает нам некоторые преимущества и недостатки по сравнению с TaskQueue.
Главная особенность микротасков: они будут исполнены сразу же, как только освобождается стек вызовов.
Например, в случае с этим стеком вызовов:

Если у нас в любой момент исполнения этого кода появляется fulfilled или rejected promise, то он будет исполнен сразу же, как только закончатся элементы в нашем стеке вызовов.
Любой JS код записывается в стек вызовов, что логично. Окончание стека вызовов, по сути, и есть окончание таска или микротаска. Интересный факт: Микротаски могут порождать другие микротаски, которые будут вызываться сразу же по освобождению стека вызовов. То есть рендер страницы может откладываться бесконечно. Это главный минус микротасков:

Если у нас 4 микротаски в очереди MicrotaskQueue, то они будут исполнены друг за другом, откладывая ререндер.
Недостаток микротасок — также и их преимущество. Например, момент срабатывания MutationObserver — это когда изменения на странице произошли, но пользователь их не увидит, пока колбек mutation observer не исполнится. Таким образом, мы можем эффективно управлять контентом, который видит пользователь.
Итого, наша картина event loop:

Мы уже разобрали, что таски могут следовать друг за другом, пропуская RenderQueue (если в ней нет тасок).
Но что исполняется внутри RenderQueue?
Рендер кадра можно поделить на несколько основных этапов. Каждый этап внутри может быть разделен на другие подэтапы (мы посмотрим это на примере Layout):

Остановимся на каждом этапе подробнее:
RequestAnimationFrame (raf)

Браузер готов начать рендер, а мы можем подписаться на это и что-то досчитать/санимировать. Этот колбек хорошо подходит для отрисовки анимаций или изменения чего-то до отрисовки следующего кадра.
Позволяет нам подписаться и запланировать изменения в DOM прямо перед отрисовкой страницы. Несколько важных фактов:
Колбек для raf принимает аргумент: DOMHighResTimeStamp — количество миллисекунд прошедших с начала жизни документа. Поэтому внутри колбека можно не брать время через perfomance.now , нужное время уже присутствует
Аналогично setTimeout , raf возвращает дескриптор ( id ), поэтому запланированный raf можно отменить через cancelAnimationFrame .
Если пользователь переключился на другой таб, свернул браузер — перерисовки страницы не происходит. Raf тоже не вызывается
JS код, который изменяет размеры элементов, считывает свойства, может зафорсить requestAnimationFrame
Как посмотреть, как часто браузер обновляет кадр? Вот так:
Вот например, мой эксперимент (запускал на hh.ru):

Style (recalculation)

Браузер пересчитывает стили, которые должны примениться из-за изменений, запланированных JS. Здесь же происходит вычисление активных media queries
Пересчет включает в себя как прямые изменения a.styles.left = ’10px’ так и те, которые описываются через CSS файлы, например element.classList.add(‘my-styles-class’) Все они будут пересчитаны с точки зрения работы CSSOM и получения Render tree.
Если запустить профилировщик и открыть сайт hh.ru, вот тут можно найти время, потраченное на Style:

Layout

Вычисление слоев, расчет положения элементов на странице, их размеров, взаимного влияния друг на друга. Чем больше DOM элементов на странице, тем тяжелее эта операция.
Современные браузеры могут организовывать рендер и layout дерева по-разному. Например, в Хроме, кроме Layout в профилировщике вы увидите такие процессы как update layer tree и layout shift , который и отвечает за сдвиг элементов относительно друг друга.
Здесь на графике выделенная строка — тот самый Layout сайта hh.ru при первом открытии приложения.

Layout — это очень болезненная операция для современных веб-сайтов. Болезненна она потому, что наиболее тяжелый style recalculation происходит при первом рендере, а вот Layout происходит при:
- Чтении свойств влияющих на размер и положение элементов (offsetWidth, offsetLeft, getBoundingClientRect, и т.д.)
- При записи свойств, влияющих на размер и положение элементов, за исключением некоторых свойств, вроде transform и will-change . Для transform браузер задействует composition процесс, а в случае с will-change браузер попытается использовать composition . Вот здесь список актуальных причин.
Layout отвечает за:
- Вычисление слоев
- Расчет взаиморасположения элементов на слое
- Расчет влияния одних элементов на другие
Layout (а вместе с ним raf и Style) может происходить не в свою очередь, когда нужно отрендерить страницу и применить изменения, а тогда, когда JS изменил размеры элементов или считал данные. Такой процесс называется force layout . Вот полный список свойств, который приводит браузер к остановке исполнения JS и вызову Layout.
Браузер не сможет рассчитать clientHeight нашего div1 без пересчета его реальных размеров. В этом случае, браузер приостановит исполнение JS кода (совсем) и выполнит по очереди: Style (чтобы определить, что изменять), Layout (чтобы определить, как изменилось). Layout должен рассчитать не только элементы, которые находятся перед, но и после div1. Современные браузеры оптимизируют расчет так, чтобы не пересчитывать абсолютно все дерево. Но в худшем случае этого не избежать. Процесс пересчета элементов называется Layout Shift . Вот так его можно посмотреть (справа список всех элементов, которые были сдвинуты и модифицированы во время Layout):

Также браузеры пытаются не форсить layout каждый раз. Поэтому, чтобы выиграть по скорости операции чтения и записи группируют:
В этом коде браузер запланировал изменение высоты div1 до 200px, но затем получил задачу на считывание. Пришлось сделать layout. Затем ситуация повторилась. Обратите внимание, браузер не произвел layout на операциях записи. Потому что в этот момент нужные данные у него уже были.
Давайте сгруппируем чтение и запись:
Такая группировка позволяет нам избежать второго layout, потому что браузер уже посчитал расположение элементов.
Layout работает со «слоями» в нашем потоке. Посмотреть на то, как браузер выделяет слои можно в chrome devtools -> More tools -> layers:

Таким образом, наш event loop превращается из одного витка в несколько, потому что и на этапе tasks, и на этапе microtasks мы можем запустить force layout:

Базовые советы для оптимизации layout:
- Уменьшать количество DOM нод
- По возможности избегать force layout
- Компоновать чтение и запись свойств
Paint

На этом шаге отрисовываем элементы, применяем стили color, background и т.д. Во время первого рендера сайта мы потратим на это достаточно времени:

В целом, эта операция не критична, так как в общем случае она не занимает много времени. Особенно после первого рендера.
После того, как пиксели нашей страницы обзавелись цветом, мы приступаем к следующему шагу — Composition.
Composition

Это единственная операция, которая в классическом веб-сайте исполняется с помощью GPU. На этом этапе браузер исполняет специфические CSS стили, например transform.
Задача этой операции: совместить слои и получить готовый кадр.
Важное дополнение: само по себе свойство transform: translate не включает рендер элемента на видеокарте. То есть сделав transform: translateZ(0) вы не «перенесете элемент на видеокарту», это заблуждение.
Однако современные браузеры могут сами переносить часть операций на GPU. Лучше всего с актуальной информацией по этому вопросу знакомиться из исходников.

Именно с помощью transform советуют создавать сложные анимации. Секрет довольно прост:
- Анимация на transform позволяет нам не вызывать layout каждый кадр, мы экономим время
- Она позволяет нам избавиться от артефактов «мыльца» при анимациях, которые иногда бывают при анимировании left, right, top, bottom
В то же время, верстать весь сайт только через transform не рекомендуется, так как это приводит к своим тормозам. Важно совмещать правильно верстку.
Как оптимизировать рендер?
Самая тяжелая операция для рендера кадра (для большинства сайтов) — layout. Сверху на графиках был layout для рендера главной страницы hh.ru. При сложных анимациях каждый кадр может пересчитываться все элементы в DOM, а это означает, что каждый кадр вы будете тратить по 13-20 мс впустую. Это ведет к пропуску кадров и проблемам с производительностью вашего сайта.

Мы можем пропустить Layout, если изменяем цвета, фоновое изображение и т.д.

Мы можем пропустить layout и paint, если изменения основаны на стиле transform и не затрагивают чтение свойств.
Итого, советы по оптимизации можно сгруппировать так:
- Выносите анимации на CSS. Исполнение JS кода не бесплатно
- Изменяйте transform свойство для “перемещения” объектов
- Используйте will-change свойство — это свойство, которое позволяет браузерам «1подготовить» дом элемент к изменениям определенных стилей. Важно — это свойство помогает браузеру понять, что разработчик запланировал изменить. Это свойство нельзя применять к большому количеству элементов, иначе вы получите тормоза.
- Используйте батчевые изменения в DOM
- Используйте requestAnimationFrame для планирования изменений в следующем кадре
- Комбинируйте задачи на запись \ чтение свойств элементов. Обращайте внимание на вызовы свойств, которые форсят layout.
- При возникновении сложных ситуаций, лучше всего запустить профилировщик и посмотреть на частоту и время вызовов. Это даст вам информацию о том, какой этап тормозит, оптимизируйте каждый этап отдельно.
Вместо заключения
В этой части мы разобрали каким образом работает Runtime в нашем браузере, его сильные и слабые стороны. Это позволяет нам:
- Понимать, как лучше писать код
- Знать в какую сторону смотреть при появлении проблем
Также мы получили достаточно глубокое понимание event loop и его составляющих:
Оптимизация JS и CSS | Корректная загрузка ресурсов сайта
Привет! Здесь постараюсь рассказать об основных приёмах оптимизации загрузки ресурсов, которые нужно знать seo-специалисту в целях ускорения сайта. В статье я дам несколько примеров, с которыми наиболее часто сталкиваются оптимизаторы в своей работе.
Итак, воспользовавшись инструментами тестирования скорости загрузки оптимизатор определяет перечень рекомендаций. Одним из основных пунктов, наряду с оптимизацией изображений, является пункт, связанный с загрузкой ресурсов, отвечающих за отображение контента на сайте.
Несмотря на то, что наиболее сильное влияние загрузка js и css оказывает на показатели Time to Interactive и Total Blocking Time. Для целей SEO, в первую очередь, важна отрисовка первого экрана. Чтобы не останавливаться на этом, ниже даю небольшой чек-лист для оптимизации FCP:
- Используйте правильную очередность загрузки ресурсов.
- Подключайте js и css по типам страниц, чтобы время загрузки не уходило на неиспользуемые файлы.
- Откажитесь от запросов @import url(«style.css»).
- Стили, влияющие на FCP расположите inline внутри html-странички.
- Минимизируйте количество js кода для отрисовки первого экрана.
— Устраните ресурсы, блокирующие основной поток
Такие ресурсы — это <script> (без defer и async) и стили <link rel=»stylesheet»>, подключаемые в <head>. Как правило, наибольшую нагрузку вызывают скрипты, подключенные через внешний ресурс, а также js сервисов веб-аналитики.
Отсюда вытекают 2 рекомендации
1. Все ресурсы нужно хранить локально. Внешний запрос может осуществляться слишком долго.
<link href=»/templatev1.css» type=»text/css» rel=»stylesheet»>
Когда вы скопируете код и сохраните его локально. У вас появятся возможности по дополнительной оптимизации. Вы сможете:
Сокращать файлы — удалять части кода, невостребованные для вашего сайта.
Минифицировать — сжимать файлы.
Комбинировать файлы — объединять несколько небольших файлов.
2. Всем ресурсам, не связанным с отображением элементов первого экрана, нужно обеспечивать асинхронную загрузку. <script async=»» src=»https://vc.ru/analytics.js»></script>
Подключение скрипта в <head> — это и есть блокировка загрузки страницы. Если вы посмотрите на свой сайт внимательно, то поймёте, что 90% всего js используется ниже первого экрана. А если это не так, то следует к этому прийти.
Располагайте скрипты в <body> и указывайте атрибуты async или defer для асинхронной загрузки:
Например, jQuery часто не нужно загружать сразу. Однако, на большинстве сайтов вы увидите, что jQuery стоит в <head> первым js ресурсом. Убрать запрос к jQuery из <head> и вызов его в <body> по необходимости будет правильным решением.
Подводя итог, скажу, что свои усилия стоит сосредоточить на устранении блокировки загрузки, т.к. второстепенные меры оптимизации являются её составными частями.
Очень поверхностная статья. Например, откладывая исполнение скриптов аналитики вы теряете часть данных. Многое можно не грузить вообще, до совершения пользователем какого-то действия. Например, скролинга. Или скрипт поиска по сайту можно не грузить, пока поле поиска не в фокусе. Про работу со шрифтами ничего не сказали, про CLS. В общем так себе статья.
Статья ради статьи, согласен
"Про работу со шрифтами" — будет отдельная статья.
Ну как бы AJAX тот же Яндекс как не понимал, так и не понимает. Да и гуглобот со скрипом до сих пор.
откладывать исполнение аналитики можно и нужно, это нормально работает, но делать это надо зная нюансы или просто иметь опыт.
На мой взгляд — это очень поверхностная статья. Например, вопрос подключения скриптов на сайт. Если у нас легаси ресурс и ранее вообще не использовались атрибуты асинхронной загрузки, то самое верное решение — defer, за счёт того, что он не блокирует отрисовка страницы и формирование dom при загрузке скриптов, а так же гарантирует порядок исполнения скроптов после события domcontentloaded, переносить скрипты из head не нужно. Использование же async в большенстве случаев вызывает race condition и самое главное, что если ресурс с качается быстрее, чем html распарсится, то он будет так же блокирующим ресурсом (банальный пример: пользователь вернулся на ваш сайт и ему из кеша идут ресурсы). Async вообще лучше использовать для различных метрик, с которыми не общается твой код, а они живут сами по себе. Использование critical css т.е. inline внесение css первого экрана в head в саму структуру html приводит к раздуванию самого html которому не всегда дают правила кеширования, т.е. вынуждая каждый раз загружать большой html. Плюс ко всему, такой подход частенько вынуждает переставать layout страницы и просаживал метрику смещения layout того же lighthouse. Моя практика использования cdn (системы распределенной поставки ресурсов) показала, что лучше с ней, чем без неё в большинстве случаев, а что бы минимизировать негативный эффект на TTFB за ресурсом, то делать prefech или preconnect в зависимости от ситуации.
Вы разработчик или SEO-специалист?
Я имею в виду — отличная тема для статьи.
Очередной раз удивляюсь.
Во первых, скорость загрузки сайта на ранжирование сайта (в контексте seo) имеет далеко не первоочередное значение. Как не пытайтесь обыгрывать эту тему — полная херня!!
Во вторых, каким боком здесь seoшник? Который может только дать рекомендации по оптимизации сайта!! На фиг ему знать как это делать? Какой, бля, seoшник полезет править dom-climata.ru, который на bitrix? Кто его туда пустит то?
В третьих, эта статья, скомканый пересказ материалов web.dev, она совсем не для этого блога! Не вешайте плиз на seoшников управление разработчиками, ибо они зашлют такого советчика ну ооочень далеко.
Обращаясь к автору: попробуйте это опубликовать на habr! Думаю даже не пропустят.)))
Про перенос вроде jQuery из head в body — очень плохой совет из далёкого прошлого. В современном вебе для скриптов, которые в любом случае понадобятся на странице, но будут нужны только после её загрузки, нужно просто добавить атрибут defer, но переносить их ниже не нужно, это только навредит общей скорости загрузки страницы.
Автору следует подтянуть свой уровень знаний, прежде чем писать статьи. Сейчас это выглядит как надёрганные по верхам устаревшие советы, которые при неправильном понимании (как у автора) могут даже навредить.
Да, про скорость автор статьи мало знает, если такие советы дает. Будьте осторожны с такими оптимизаторами.
Добавлю ко всему вышесказанному, что про внешние ресурсы тоже тема не раскрыта.
Даже если изогнуться и выкачать ту же Яндекс Метрику или Google Analytics, решить вопрос с их обновлением через крон, то остается проблема подключения внешних скриптов, подключаемых в основном файле.
Например качаем себе метрику. В самом файле watch.js подключаются дополнительные файлы.
Получается нужно их тоже выкачивать? Тогда в коде основного файла нужно менять пути к скриптам, что противоречит автоматическому обновлению файлов через Cron.
Если исключить автоматическое обновление — то нужно следить за статистикой, чтобы не пропустить момент когда что-то изменится в коде и отслеживание прекратит работу.
Как оптимизировать CSS сайта для улучшения Core Web Vitals ✈

В 2021 году Google сформировал Web Vitals — представление о сигналах качества сайта, отражение того, насколько удобно пользователям с ним взаимодействовать. Core Web Vitals — набор показателей для количественного измерения этого удобства.
Кратко самое важное про Core Web Vital
В набор показателей Core Web Vitals входит измерение времени загрузки элементов страницы, отзывчивость контента на действия пользователей и стабильность этого контента во время загрузки.
LCP (Largest Contentful Paint) — загрузка самого большого видимого объекта на видимой пользователю области экрана. Хорошим показателем считается 2,5 секунды с начала загрузки страницы.
CLS (Cumulative Layout Shift) — визуальная стабильность, смещение макета страницы во время загрузки элементов. Приемлемый показатель CLS — 0,1 или меньше.
FID (First Input Delay) — интерактивность, время реакции страницы на первое действие пользователя. Хороший уровень FID — до 100 миллисекунд.
Подробнее Core Web Vitals мы разбирали в материале.
CSS тоже могут влиять на Core Web Vitals, особенно это касается показателей LCP — отрисовки самого большого видимого элемента, и CLS — сдвига макета.
Проверить скорость загрузки страницы и посмотреть на процесс ее загрузки можно в бесплатном инструменте от PR-CY.
Как оптимизировать CSS для быстрой загрузки страниц:
Для удобства методы разбиты по элементам, с которыми нужно работать: макет, изображения, шрифты, анимация — все касается работы со стилями. За основу взяли статью «CSS для Web Vitals» Кэти Хемпениус и Уны Кравец, перевели и дополнили полезными решениями.
1. Критические и некритические CSS
При обработке CSS по умолчанию блокируется рендеринг — визуализация страницы. Пока браузер не загрузит и не проанализирует таблицу стилей, он не будет загружать другие ресурсы. Если файлы CSS большие или есть проблемы с сетью, показатель LCP может ухудшиться, потому что затянется загрузка наибольшего видимого объекта.
Как оптимизировать CSS
Производительность можно повысить, если разделить CSS на критически важные для загрузки первого экрана и остальные, которые можно отложить.
Верхняя строка отображает загрузку страницы с CSS, блокирующим рендеринг. Нижняя — загрузка той же страницы со встроенным критическим CSS:
Сравнение загрузки страницы
Как работать с CSS страницы
Критические CSS вынести в head
CSS, нужные для загрузки верхней части первого экрана, нужно как можно быстрее отобразить пользователям. Встройте их в head, чтобы для получения этих стилей не требовался дополнительный запрос.
Некритические CSS загрузить асинхронно
Остальную часть CSS, которая не требуется для начального рендеринга страницы, можно загрузить асинхронно с помощью loadCSS или отложить в footer.
Неиспользуемые CSS
Ненужные CSS удалить или переместить в другую таблицу стилей, если для других страниц сайта они все-таки нужны. Лучше не делать это вручную, можно удалить что-то нужное.
В статье про оптимизацию LCP мы разобрали, как автоматизировать этот способ оптимизации стилей. Смотрите раздел «Блокировка рендеринга JavaScript и CSS»
У способа есть и недостатки: браузер не будет кэшировать встроенный в head CSS, так что каждый раз он будет загружаться заново. Проверьте, возможно, у вас нет проблем с рендерингом, так что и разделять CSS вам не нужно.
Еще можно минифицировать CSS, то есть сократить, тем самым уменьшив размер файла. После минификации в файле не будет лишних пробелов и ненужных символов. Бесплатные инструменты для этого собрали в статье об ускорении загрузки.
2. Изображения
Рассмотрим не оптимизацию изображений в целом, а только проблемы, связанные с CSS и картинками.
Изображения и LCP
LCP — показатель загрузки самого большого элемента на видимом экране. Чаще всего это изображение, хотя самым большим элементом может быть и текст, и видеоролик.
Более того, самый большой элемент страницы может меняться по мере загрузки и появления новых объектов. Или в зависимости от устройства просмотра, как в этом примере. Здесь главными объектами выступают обложка статьи, фон уведомления о файлах cookie и текст статьи:
Варианты самого большого объекта в зависимости от устройства
Как оптимизировать
В примере на планшете самый большой элемент — это фоновое изображение уведомления о файлах cookie. Вместо него можно нарисовать градиент в CSS, чтобы не тратить время на загрузку изображения для фона. Это улучшит LCP.
Измените .banner CSS, чтобы использовать градиент CSS, а не изображение.
Сдвиги макета CLS
Если картинка загружается после отрисовки страницы, а под ее параметры не было зарезервировано место, макет сдвинется. Это будет заметно при медленном интернете или при большом размере файла.
Как исправить
Нужно зарезервировать место в DOM для элемента, появление которого смещает макет.
Можно зарезервировать нужное для картинки пространство с помощью полей соотношения сторон CSS. Если ширина изменится, изменится и высота, сохранив пропорции.
Если известно одно измерение изображения, можно определить второе по его пропорциям. Если картинка шириной 640 пикселей имеет соотношение сторон 16:9, то его высота составит 640 x (9/16) = 360 пикселей. Чтобы не считать самим, есть калькулятор.
Для предотвращения сдвигов макета как раз полезно использовать способ, учитывающий соотношение сторон. Об этом способе есть материал на английском.
Современные браузеры устанавливают соотношение сторон изображений по умолчанию на основе атрибутов ширины и высоты, благодаря CSS. Разработчикам нужно установить width и height как обычно:
И использовать каскадные CSS для всех браузеров, которые добавят соотношение сторон по умолчанию на основе существующих атрибутов width и height:
В этом случае браузер вычислит пропорцию картинки, основанную на атрибутах width и height, до загрузки изображения, в самом начале расчета макета. Если изображение имеет определенную ширину (например width: 100%), с помощью соотношения сторон он вычислит его высоту.
Адаптивные изображения
Современный подход к загрузке изображений — использование srcset атрибутов в сочетании с атрибутами width и height. Способ дает дополнительное преимущество в производительности, потому что позволяет обслуживать изображения разного размера на разных устройствах.
Для примера это выглядит так:
Изображение в контейнере
Если изображение находится в контейнере, вы можете использовать CSS, чтобы изменить размер изображения до ширины этого контейнера. Прммер: установили height: auto, чтобы высота изображения не была фиксированным значением:
3. Реклама, скрипты, фреймы
История такая же, как в предыдущем пункте про изображения. Если какой-то элемент появляется в DOM после того, как окружающая страница уже отрисована, он сдвинет контент под ним. Типичные примеры — появление в верхней части страницы рекламы, фреймов, в целом сторонних скриптов, когда элементы ниже уже загрузились.
Как реклама может портить CLS:
сайт вставляет рекламный контейнер в DOM;
сайт меняет размер рекламного контейнера с помощью собственного кода;
рекламное объявление заполняет контейнер, но оказывается другого размера.
Динамические элементы тоже могут смещать контент. К примеру, это часто происходит из-за форм и баннеров «подпишитесь на рассылку», «другой контент по теме», «установите наше приложение», «закажите у нас» и других.
Посмотреть, какие элементы влияют на смещение макета страницы можно в бесплатном инструменте для проверки скорости. Он анализирует загрузку страницы поэтапно и дает подсказки, как оптимизировать каждый этап.
Фрагмент результатов теста
Как решить проблему
Рекламные блоки и фреймы
Зарезервировать место для рекламы или фрейма, чтобы никакой контент не занял этот слот, пока элементы не загрузились.
Если слот для рекламы есть, но само объявление не появилось, не стоит сворачивать этот рекламный слот на странице. Лучше показывать там заглушку, чтобы не сдвигать контент.
Если в слоте под рекламу появляются объявления разного размера, ориентируйтесь на максимально возможный размер, когда резервируете место, чтобы не обрезать объявления. Максимально возможный из тех, что когда-либо появлялись в этом блоке.
Динамический контент
Не стоит загружать новый контент над уже загруженным и прочитанным пользователями. Скорее всего появление нового блока сверху будет для них неожиданностью. Исключением могут быть элементы, которые появились в ответ на действие пользователя и ожидаются вверху экрана.
Если такой блок нужен вверху, также зарезервируйте для него место на странице.
Уведомления cookie
Бывает, что источником сдвигов макета выступают уведомления о согласии на использование файлов cookie, если они находятся в верхней части экрана. Как можно с ним поработать:
Использовать липкий колонтитул или модальное окно для уведомления о файлах cookie. При этих подходах уведомление появится наложением поверх остальной части страницы и тогда оно не будет вызывать смещение макета при загрузке.
Само уведомление лучше перенести вниз и настроить фиксированное или абсолютное позиционирование. При фиксированном элемент закрепится и во время прокрутки страницы будет всегда виден посетителю, а при абсолютном будет перемещаться с документом при прокрутке. Такое уведомление перекроет статичные элементы, даже если будет находиться ниже в коде.
- Скрипты, которые загружаются не асинхронно, будут задерживать загрузку страницы и портить показатель LCP. Поэтому нужно настроить асинхронную загрузку, чтобы она не влияла на основной поток рендера. Для этого нужно добавить async в тег элемента:
4. Шрифты
Увидеть шрифты, которые загружаются на определенной странице, можно в браузерном режиме просмотра кода страницы. Перейдите на вкладку Network и выберите фильтр по Font:
Просмотр шрифтов на странице
Узнать, сколько времени требуется для запроса шрифта, можно на вкладке «Timing». Чем раньше запрошен шрифт, тем скорее его можно будет загружать и использовать. Цепочка запросов на шрифт отображается на вкладке «Initiator», чем она короче, тем быстрее можно будет запросить шрифт.
Цепочка запросов для шрифта
Применение шрифтов может задерживать рендеринг текста и вызывать сдвиги макета. Посмотрим, какие проблемы могут возникнуть и как их решать.
Как работать со шрифтами
Как оптимизировать рендеринг
Есть два варианта рендеринга шрифтов:
Резервный невидимый шрифт —> основной шрифт
Текст рендерится с доступным системе шрифтом, но он не видим пользователю до загрузки. Когда основной шрифт загружается, текст становится виден на странице. В это время может быть заметно FOIT — мигание невидимого текста.
В этом случае сдвиг макета минимальный, но скорость загрузки будет ниже, может задержаться первая отрисовка содержимого — FCP, а иногда и отрисовка самого большого объекта — LCP.
Системный шрифт —> основной шрифт
Текст рендерится с доступным шрифтом, но видимым пользователям, а после загрузки шрифт подменяется. Может быть заметно ощутимое мигание нестилизованного текста — FOUT.
При этом способе загрузка быстрее, но сдвиг более вероятен.
У PageSpeed Insights есть замечание «Настройте показ всего текста во время загрузки веб-шрифтов», то есть инструмент советует второй вариант.
Для этого в CSS шрифта нужно добавить свойство «font-display:swap», чтобы до загрузки файла вашего шрифта текст отображался системным шрифтом.
В нужном CSS-файле шрифта будут правила font-face с прописанными путями к файлам шрифта. Внутрь этого правила и нужно вставить «font-display:swap».
Стили внешних Google-шрифтов редактировать не получится. Нужно сделать правку в файле, где записан адрес шрифта: в конец адреса добавить параметр display=swap.
Для сайтов на WordPress есть плагины.
Полезный сервис: Transfonter в ответ на файл шрифта выдаст архив со всеми форматами и корректный CSS.
Как избежать смещения макета
Замена резервного шрифта на основной может повлиять на CLS — смещение макета, если основной и резервный шрифты занимают разное пространство на странице. В этом примере замена шрифта привела к смещению элементов страницы вверх на пять пикселей:
Смещение макета из-за шрифтов
Минимизировать эти сдвиги можно, если подобрать шрифты с одинаковыми пропорциями букв.
Предзагрузка
Без предзагрузки браузер сначала прочитает страницу, увидит указание на CSS, скачает его, разберет, найдет блок с описанием шрифтов и только потом начнет их загружать. Из-за этого могут быть сдвиги макета и в целом замедление загрузки.
Файлы шрифтов можно предзагрузить. Если браузер увидит объявление о предзагрузке шрифта, он будет загружать и его, и параллельно CSS. К моменту, как он разберет CSS, шрифт уже будет доступен для рендера.
Для WordPress есть плагины, либо можно использовать link rel = «preload» as = «font».
В примере есть версия шрифта v=4.5.0. Ее нужно использовать в preload, если Google показывает версию в адресе.
Что касается Google Fonts, то он предоставляет возможность загружать шрифты с помощью тегов link или операторов @import. Подсказка preconnect в теге link должна привести к более быстрой загрузке таблиц стилей, чем при использовании @import.
Удалите из таблицы стилей этот оператор @import:
И добавьте в head документа эти теги link:
Эти теги подсказывают браузеру, что нужно установить раннее соединение с источниками, используемыми Google Fonts, и загрузить таблицу стилей шрифтов Montserrat и Roboto.
Есть и другие способы ускорить загрузку страницы и оптимизировать все параметры, входящие в Core Web Vitals. Но эти улучшения, касающиеся CSS, тоже могут сделать страницу значительно удобнее и приятнее для пользователей.