Какво е Cumulative Layout Shift (CLS)?
Cumulative Layout Shift (CLS) е една от трите основни метрики в Core Web Vitals и измерва визуалната стабилност на уеб страницата. Докато LCP следи скоростта на зареждане, а INP — отзивчивостта, CLS количествено определя колко често потребителите изпитват неочаквани премествания на съдържанието.
Ако някога сте се опитвали да кликнете върху бутон, а той се е преместил точно в последния момент — да, точно това измерва CLS.
Layout shift възниква, когато видим елемент промени позицията си между два последователни рендер кадъра, без потребителят да е направил нещо. Банер, който изскача отгоре и избутва текста надолу, или бутон „Отказ", който се премества и вместо него натискате „Потвърди" — класически CLS кошмари.
Как се изчислява CLS резултатът?
Формулата не е сложна — CLS резултатът се получава чрез умножение на два фактора:
- Impact fraction — каква част от viewport-а е засегната от преместването
- Distance fraction — на какво разстояние са се преместили елементите
Или казано по-просто:
CLS резултат = impact fraction × distance fraction
Браузърът групира последователните премествания в т.нар. session windows — прозорци с максимална продължителност от 5 секунди, в които отделните shifts са разделени от не повече от 1 секунда. Финалният CLS резултат е най-големият session window.
Прагове за оценка
- Добър: 0.1 или по-малко
- Нуждае се от подобрение: между 0.1 и 0.25
- Лош: над 0.25
Важно: Google използва 75-ия перцентил от реални потребителски данни (CrUX) за класиране, не Lighthouse лабораторни резултати. В Lighthouse 12 CLS определя 25% от общия Performance резултат, така че влиянието му е значително.
Защо CLS е критичен за SEO и потребителското изживяване?
CLS е пряко свързан с т.нар. Rage Clicks — когато потребител кликне върху грешен елемент заради преместване на съдържанието. Честно казано, малко неща дразнят потребителите повече от това.
Представете си: потребител иска да натисне „Отказ", но layout shift точно преди кликването го кара да натисне „Потвърди поръчка". Катастрофален UX провал.
В 2026 г. алгоритъмът на Google все повече приоритизира концепцията за Experience Equity — стабилно и отзивчиво изживяване на всички устройства, не само на най-бързия хардуер. Така че CLS оптимизацията не е просто „хубаво е да има", а реална необходимост.
8-те най-чести причини за лош CLS
1. Изображения и видео без зададени размери
Това е абсолютно най-честата причина. Когато браузърът срещне <img> или <video> таг без атрибути width и height, той заделя нула място за елемента. След зареждането — бум — всичко се пренарежда надолу.
2. Реклами, embed-и и iframes без резервирано пространство
Рекламните мрежи динамично зареждат и оразмеряват рекламите. Без предварително зададени контейнерни размери, всяка реклама предизвиква каскада от премествания. Ако сте работили с Google AdSense, знаете за какво говоря.
3. Уеб шрифтове (FOIT/FOUT)
Шрифтовете са тих убиец на CLS. Flash Of Unstyled Text (FOUT) се случва, когато страницата първо показва текст с fallback шрифт, а после го сменя с уеб шрифт с различни ширини на символите, line-height и пр. Резултатът? Целият текст се преаранжира.
4. Динамично инжектирано съдържание
JavaScript, който вмъква банери, cookie известия или промоционални ленти над съществуващо съдържание — основна причина за CLS, особено в React приложения.
5. Каруселни елементи с неоптимизирани анимации
Авто-плейващи карусели, които използват анимации, променящи layout свойства (width, height, top), могат да генерират безкрайни layout shifts. Карусели, които причиняват shifts — по-добре без тях.
6. Lazy loading на above-the-fold изображения
Често срещана грешка: прилагане на lazy loading за изображения, видими при първоначално зареждане, без зададени размери. Всяко такова зареждане предизвиква shift.
7. SPA навигации и преходи
При Single Page Application преходи, ако анимацията или зареждането на ново съдържание отнеме повече от 500 мс, преместванията се отчитат като неочаквани. Това е нещо, което лесно се пропуска при разработка.
8. Skeleton screens с грешни размери
Skeleton елементи, които не съответстват на реалната височина на финалното съдържание, причиняват shift при замяната си. Иронично — опитвате се да подобрите UX, а влошавате CLS.
10 стратегии за оптимизация на CLS
Добре, нека минем към конкретните решения. Ето 10 стратегии, които наистина работят.
1. Винаги задавайте width и height на медийни елементи
Модерните браузъри използват атрибутите width и height, за да изчислят aspect ratio и да резервират място преди зареждането. Просто ги добавете на всеки <img> и <video>:
<img
src="hero.webp"
alt="Hero image"
width="1200"
height="630"
loading="eager"
fetchpriority="high"
/>
За responsive изображения използвайте CSS aspect-ratio:
.hero-image {
width: 100%;
height: auto;
aspect-ratio: 1200 / 630;
}
2. Резервирайте пространство за реклами и динамично съдържание
Правилото тук е просто: никога не вмъквайте съдържание над съществуващо, освен ако не е в отговор на потребителско действие. За реклами задайте минимална височина на контейнера:
.ad-container {
min-height: 250px;
width: 300px;
background: #f0f0f0;
/* Предотвратява колапсиране при празен контейнер */
contain: layout size;
}
За cookie банери и известия? Използвайте фиксирано позициониране — така не изместват нищо:
.cookie-banner {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
}
3. Оптимизирайте зареждането на шрифтове
Комбинирайте font-display: swap с preloading и подберете fallback шрифт, максимално близък до уеб шрифта. Ето какво работи добре на практика:
<!-- Preload критичните шрифтове -->
<link
rel="preload"
href="/fonts/Inter-Regular.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<style>
@font-face {
font-family: 'Inter';
src: url('/fonts/Inter-Regular.woff2') format('woff2');
font-display: swap;
/* Фин тунинг на fallback за минимален CLS */
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
}
body {
font-family: 'Inter', 'Segoe UI', system-ui, sans-serif;
}
</style>
CSS свойствата size-adjust, ascent-override и descent-override са малко познати, но изключително полезни — позволяват фино настройване на fallback шрифта, така че да съвпада максимално с уеб шрифта.
4. Използвайте CSS transform за анимации
Анимации, които променят height, width, top или left, предизвикват layout recalculation и генерират CLS. Вместо тях — transform и opacity, които са GPU-ускорени и не причиняват layout shifts:
/* ❌ Лошо — причинява layout shift */
.notification-enter {
animation: slideDown 0.3s ease;
}
@keyframes slideDown {
from { height: 0; }
to { height: 60px; }
}
/* ✅ Добре — без layout shift */
.notification-enter {
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from { transform: translateY(-100%); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
5. Приложете CSS Containment
CSS свойството contain инструктира браузъра да изолира рендеринга на определени секции. На практика contain: layout size казва: „Нищо вътре в тази кутия не може да промени layout-а на нищо извън нея":
.sidebar-widget {
contain: layout size;
/* Браузърът знае, че layout shifts вътре
няма да каскадират навън */
}
.card-grid {
contain: layout;
/* Добавянето на нови карти няма да
повлияе на елементите извън grid-а */
}
6. Използвайте content-visibility с contain-intrinsic-size
content-visibility: auto казва на браузъра да пропусне рендеринга на елементи извън viewport-а, намалявайки началното rendering време с до 40%. Звучи страхотно, но има уловка — без contain-intrinsic-size елементите ще имат височина 0 и ще причинят CLS при скролиране:
.article-section {
content-visibility: auto;
contain-intrinsic-size: auto 500px;
/* 'auto' позволява браузърът да запомни
реалния размер след първия рендер */
}
Тази техника е особено ефективна за дълги страници — блог постове, продуктови каталози и документации.
7. Осигурете съвместимост с bfcache
Back/forward cache (bfcache) съхранява страниците в паметта на браузъра. При навигация назад/напред страницата се възстановява мигновено — без никакви layout shifts. Това е, по мое мнение, една от най-подценените техники за нисък CLS:
// Проверка за bfcache съвместимост
window.addEventListener('pageshow', (event) => {
if (event.persisted) {
console.log('Страницата е възстановена от bfcache');
}
});
// ❌ Тези неща блокират bfcache:
// - unload event listeners
// - Cache-Control: no-store
// - Незатворени WebSocket или IndexedDB връзки
// ✅ Вместо unload, използвайте pagehide:
window.addEventListener('pagehide', (event) => {
// Почистване на ресурси
});
Тествайте bfcache съвместимост в Chrome DevTools: Application tab → Back/forward cache. Отнема буквално секунди.
8. Правилно управлявайте lazy loading
Никога не прилагайте lazy loading на above-the-fold изображения — това е грешка, която виждам изненадващо често. Използвайте loading="eager" и fetchpriority="high" за LCP елемента, а loading="lazy" само за below-the-fold:
<!-- Above the fold — зарежда се веднага -->
<img
src="hero.webp"
width="1200"
height="630"
loading="eager"
fetchpriority="high"
alt="Hero"
/>
<!-- Below the fold — lazy loading с размери -->
<img
src="product.webp"
width="400"
height="300"
loading="lazy"
alt="Product"
/>
9. Стабилизирайте skeleton screens и shared layouts
Skeleton елементите трябва да съвпадат точно с размерите на финалното съдържание. При SPA навигации shared layout-ите (header, sidebar) не трябва да променят височината си между routes:
/* Skeleton с фиксирани размери, съвпадащи
с финалния елемент */
.card-skeleton {
width: 100%;
height: 320px; /* Точно колкото .card */
border-radius: 8px;
background: linear-gradient(
90deg,
#f0f0f0 25%,
#e0e0e0 50%,
#f0f0f0 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
10. Мониторинг с web-vitals и RUM данни
Лабораторните данни показват само една страна на нещата — CLS при началното зареждане. В реалния свят потребителите предизвикват CLS при скролиране, взаимодействие с менюта и навигация в SPA. За това ви трябва Real User Monitoring:
import { onCLS } from 'web-vitals/attribution';
onCLS((metric) => {
const entry = metric.entries[metric.entries.length - 1];
console.log('CLS стойност:', metric.value);
console.log('Най-голям shift клъстер:', metric.attribution.largestShiftTarget);
console.log('Елемент източник:', metric.attribution.largestShiftSource);
// Изпращане към аналитична платформа
navigator.sendBeacon('/api/vitals', JSON.stringify({
metric: 'CLS',
value: metric.value,
target: metric.attribution.largestShiftTarget,
source: metric.attribution.largestShiftSource,
url: window.location.href,
timestamp: Date.now()
}));
});
Един съвет от практиката: проверете P75 vs. P95 разпределението в CrUX Dashboard. Ако вашият P75 CLS е 0.05 (добър), но P95 е 0.40 (лош), това означава, че 5% от потребителите — обикновено на по-стари устройства — имат катастрофално изживяване. Тези 5% имат значение.
Дебъгване на CLS с Chrome DevTools
Live Metrics View
Отворете Performance панела в Chrome DevTools — веднага ще видите текущите LCP и CLS метрики с оценка (добър, нуждае се от подобрение, лош). В табовете Interactions и Layout Shifts ще намерите таблици с детайлна информация за всяко преместване.
Запис на Performance Trace
За по-детайлен анализ направете следното:
- Отворете DevTools → Performance
- Натиснете Record и заредете страницата
- В Layout Shifts track ще видите лилави ленти — това са shift clusters
- Диамантите показват отделни shifts, а размерът им е пропорционален на тежестта
- Кликнете върху shift, за да видите в Summary панела кой елемент е виновен
Layout Shift Culprits
Layout Shift Culprits в Chrome DevTools автоматично идентифицира причините за layout shifts с етикети като:
- Injected iframe — iframe, инжектиран в страницата
- Font request — уеб шрифт, причинил преместване при зареждане
- Unsized images — изображения без зададени размери в HTML
Тази функция спестява доста време при дебъгване — вместо да гадаете, директно виждате виновника.
Layout Shift Regions
Активирайте визуализацията: DevTools → Settings → More Tools → Rendering → Layout Shift Regions. Областите с layout shift ще се подсветят в лилаво в реално време. Много полезно при first load.
Актуализации на CLS метриката в Chrome 2025–2026
Chromium екипът продължава да подобрява точността на CLS. Ето какво се промени напоследък:
- CLS вече правилно засича премествания на fixed position елементи
- Правилно засичане на премествания на деца на sticky елементи
- Без наказание за
content-visibility: autoсъдържание (най-после) - Игнориране на layout shifts при промяна на
visibility: hiddenкъм видимо - Обработка на clipping при елементи с
contain: paint - Игнориране на layout shifts от видео slider thumb взаимодействия
Накратко — по-точни резултати и по-малко фалшиви положителни. Добра новина за всички, които мониторират CLS в production.
Чеклист за CLS оптимизация
Преди да публикувате или деплойнете, минете през този списък:
- Всички
<img>и<video>иматwidthиheightатрибути - Responsive изображения използват
aspect-ratioв CSS - Рекламни контейнери имат
min-height - Уеб шрифтовете са preloaded с
font-display: swap - Fallback шрифтовете са настроени с
size-adjustиascent-override - Анимациите използват
transformвместо layout свойства - CSS containment е приложен на изолирани секции
content-visibility: autoе комбиниран сcontain-intrinsic-size- Страницата е съвместима с bfcache
- Lazy loading не е приложен на above-the-fold елементи
- Skeleton screens съвпадат с размерите на финалното съдържание
- Динамичното съдържание не се вмъква над съществуващо
- RUM мониторинг с web-vitals библиотеката е активен
Често задавани въпроси
Каква е разликата между лабораторни и полеви данни за CLS?
Лабораторните данни (Lighthouse) измерват CLS само при първоначалното зареждане в контролирана среда. Полевите данни (CrUX, web-vitals RUM) отчитат CLS по време на целия жизнен цикъл на страницата — включително при скролиране, взаимодействие с менюта и SPA навигации. Затова полевите CLS стойности почти винаги са по-високи от лабораторните.
Всички layout shifts ли се отчитат в CLS?
Не. Layout shifts, предизвикани от потребителско взаимодействие (клик, натискане на клавиш) в рамките на 500 мс, се считат за „очаквани" и не влизат в CLS резултата. Само неочакваните премествания — без потребителска намеса — се броят.
Как CLS влияе на SEO класирането?
CLS е една от трите Core Web Vitals метрики, които Google използва като сигнал за класиране. В комбинация с LCP и INP, добрият CLS резултат помага за по-добра позиция в търсачката — особено при конкурентни заявки, където съдържанието е сходно.
Може ли content-visibility: auto да причини CLS?
Да, ако не е комбинирано с contain-intrinsic-size. Когато елемент с content-visibility: auto е извън viewport-а, браузърът му задава височина 0. При скролиране до него реалната височина се изчислява и получавате shift. Решението: винаги добавяйте contain-intrinsic-size: auto [очаквана височина].
Как да дебъгвам CLS проблеми на мобилни устройства?
Използвайте Chrome DevTools с Remote Debugging за реално мобилно устройство, или проверете CrUX данни, сегментирани по тип устройство. Мобилните устройства често имат по-висок CLS заради по-бавно зареждане на шрифтове, по-малък viewport (където един shift заема по-голяма част от екрана) и различно поведение на рекламните мрежи.