Prečo je vizuálna stabilita šiestym pilierom webového výkonu
V predchádzajúcich článkoch sme sa venovali optimalizácii obrázkov, navigácie, doručovaniu kódu, cachovaniu a odozve na interakciu (INP). Teraz prichádza šiesty — a úprimne, často dosť podceňovaný — pilier: vizuálna stabilita stránky. Metrika Cumulative Layout Shift (CLS) meria, koľko neočakávaného pohybu obsahu používateľ zažije počas celej životnosti stránky.
A práve tu mnohé weby zlyhávajú. Podľa globálnych dát z CrUX reportu má v roku 2026 stále až 38 % mobilných stránok CLS skóre označené ako „potrebuje zlepšenie" alebo „zlé". To je dosť alarmujúce číslo.
Predstavte si situáciu: čítate článok na mobile, chcete kliknúť na odkaz, no v poslednej milisekunde sa nad text vloží reklamný banner a vy namiesto odkazu kliknete na reklamu. Frustrujúce, že? Presne toto CLS meria a presne preto Google túto metriku zaradil medzi tri Core Web Vitals — spoločne s LCP a INP.
Čo presne CLS meria a ako sa vypočítava
CLS nie je jednoduchý súčet všetkých posunov — bolo by to príliš jednoduché. Google v roku 2024 definitívne prešiel na metódu session windows, čo sú v podstate skupiny layout shiftov, ktoré sa vyskytujú v rámci krátkeho časového okna. Konkrétne to funguje takto:
- Session window začína prvým layout shiftom a trvá maximálne 5 sekúnd
- Medzi jednotlivými shiftami v rámci okna nesmie uplynúť viac ako 1 sekunda
- CLS skóre stránky je najväčšia hodnota spomedzi všetkých session windows
Každý jednotlivý layout shift sa vypočíta podľa vzorca:
layout shift score = impact fraction × distance fraction
Impact fraction je percento viewportu, ktoré zasiahnuté elementy zaberajú (pred aj po posune). Distance fraction je najväčšia vzdialenosť, o ktorú sa element posunul, vyjadrená ako podiel výšky viewportu.
Google považuje CLS skóre pod 0,1 za dobré, medzi 0,1 a 0,25 za vyžadujúce zlepšenie a nad 0,25 za zlé. Tieto prahové hodnoty sa merajú na 75. percentile — takže minimálne 75 % návštev stránky musí mať CLS pod 0,1, aby ste boli v zelenej zóne.
Dôležité: kedy sa layout shift nepočíta
Toto je detail, ktorý veľa vývojárov prekvapí. Layout shifty, ktoré nastanú do 500 milisekúnd po interakcii používateľa (kliknutie, ťuknutie, stlačenie klávesu), sa do CLS nepočítajú. Prehliadač ich označí príznakom hadRecentInput a vylúči z výpočtu. Takže ak používateľ klikne na tlačidlo „Zobraziť viac" a obsah sa rozbalí, tento posun CLS neovplyvní. Dáva to zmysel — používateľ ten posun očakával.
Najčastejšie príčiny zlého CLS
Tak, poďme na to. Podľa analýz v roku 2026 sú hlavní vinníci zlého CLS títo:
- Obrázky a videá bez dimenzií — 62 % mobilných stránok má obrázky bez atribútov width/height (áno, v roku 2026)
- Webové fonty s odlišnými metrikami — len 11 % stránok preloaduje webové fonty
- Dynamicky vkladaný obsah — reklamy, cookie bannery, notifikačné lišty
- CSS animácie používajúce layout vlastnosti — 40 % stránok má nekomponované animácie
- Externé skripty tretích strán — oneskorene načítané widgety a embedy
- Lazy loading LCP elementu — nesprávne aplikovanie lazy loadingu na hlavný obsah
Poďme si jednotlivé problémy rozobrať a ukázať konkrétne riešenia.
Oprava č. 1: Obrázky a médiá — rozmery a aspect-ratio
Najjednoduchšia a zároveň najúčinnejšia oprava CLS je pridanie atribútov width a height ku všetkým elementom <img> a <video>. Znie to banálne, ale je to neuveriteľne účinné. Moderné prehliadače z nich automaticky vypočítajú aspect ratio a rezervujú priestor ešte pred načítaním súboru.
<!-- Zlé: žiadne rozmery, prehliadač nevie koľko miesta rezervovať -->
<img src="hero.webp" alt="Hero obrázok">
<!-- Dobré: prehliadač vypočíta aspect ratio z atribútov -->
<img src="hero.webp" alt="Hero obrázok" width="1200" height="630">
Pre responzívne obrázky, kde nechcete fixnú šírku v pixeloch, použite CSS vlastnosť aspect-ratio:
.hero-image {
width: 100%;
height: auto;
aspect-ratio: 16 / 9;
}
/* Pre kontajnery s obrázkami na pozadí */
.card-thumbnail {
aspect-ratio: 4 / 3;
background-size: cover;
background-position: center;
}
Responzívne obrázky s picture elementom
Pri použití elementu <picture> s viacerými zdrojmi je tu jeden dôležitý detail — atribúty width a height sa nastavujú na vnútorný element <img>, nie na <source>. Toto sa ľahko prehliadne:
<picture>
<source srcset="hero.avif" type="image/avif">
<source srcset="hero.webp" type="image/webp">
<img src="hero.jpg" alt="Hero" width="1200" height="630"
loading="lazy" decoding="async">
</picture>
Oprava č. 2: Webové fonty — size-adjust a metrické prepísanie
Webové fonty sú takým tichým zabijakom CLS. Keď sa fallback font (povedzme Arial) nahradí webovým fontom (trebárs Inter), text sa môže preformátovať — iný počet riadkov, iné medzery, iná výška znakov. Tomuto efektu sa hovorí FOUT (Flash of Unstyled Text) a je to niečo, čo dokáže poriadne znepríjemniť používateľský zážitok.
Tradičné riešenie font-display: swap zabezpečí, že text je okamžite viditeľný, no nezabraňuje posunu pri výmene fontu. Našťastie, v roku 2026 máme oveľa lepšie nástroje.
Deskriptor size-adjust v @font-face
Deskriptor size-adjust umožňuje upraviť veľkosť fallback fontu tak, aby čo najlepšie zodpovedal metrikám webového fontu. Spolu s deskriptormi ascent-override, descent-override a line-gap-override dokážete dosiahnuť takmer dokonalú zhodu:
/* Definícia fallback fontu s metrickým prepísaním */
@font-face {
font-family: "Inter-fallback";
src: local("Arial");
size-adjust: 107.64%;
ascent-override: 90.49%;
descent-override: 22.48%;
line-gap-override: 0%;
}
/* Definícia webového fontu */
@font-face {
font-family: "Inter";
src: url("/fonts/inter-v13-latin-ext-regular.woff2") format("woff2");
font-display: swap;
font-weight: 400;
}
/* Použitie s fallback reťazcom */
body {
font-family: "Inter", "Inter-fallback", sans-serif;
}
Správne hodnoty pre size-adjust a ďalšie deskriptory nájdete pomocou nástrojov ako Fallback Font Generator alebo priamo v Next.js, ktorý tieto hodnoty generuje automaticky cez modul next/font. Osobne som najlepšie skúsenosti mal práve s Fallback Font Generátorom — je rýchly a výsledky sú presné.
Preloadovanie kritických fontov
Kombinácia metrického prepísania s preloadovaním fontov minimalizuje čas medzi zobrazením fallbacku a načítaním webového fontu:
<link rel="preload" href="/fonts/inter-v13-latin-ext-regular.woff2"
as="font" type="font/woff2" crossorigin>
Dôležité: Preloadujte maximálne 1-2 fonty. Vážne. Preloadovanie 4 a viac fontov v skutočnosti výkonu škodí, pretože súťažia s ostatnými kritickými zdrojmi o bandwidth.
Oprava č. 3: Dynamicky vkladaný obsah — rezervácia priestoru
Cookie bannery, notifikačné lišty, promočné bary a inline reklamy sú najčastejším zdrojom CLS na komerčných weboch. Problém je vždy rovnaký — tieto elementy sa vložia nad existujúci obsah a „zatlačia" ho nadol.
Stratégia č. 1: Pevná výška kontajnera
/* Rezervácia miesta pre promočný banner */
.promo-banner-slot {
min-height: 60px; /* Presná výška bannera */
contain: layout; /* Zabrání šíreniu layout zmien */
}
/* Skeleton loader pre async obsah */
.product-card-skeleton {
min-height: 320px;
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; }
}
Stratégia č. 2: Overlay namiesto vkladania
Cookie bannery a notifikácie by nikdy nemali posúvať obsah stránky. Toto je celkom jednoduchá zásada, ktorú ale prekvapivo veľa webov porušuje. Použite position: fixed alebo position: sticky:
/* Cookie banner ako overlay — nulový vplyv na CLS */
.cookie-banner {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
transform: translateY(100%);
transition: transform 0.3s ease-out;
}
.cookie-banner.visible {
transform: translateY(0);
}
Stratégia č. 3: Vkladanie obsahu pod existujúci obsah
Ak musíte dynamicky pridať elementy, vkladajte ich pod existujúci obsah, nie nad neho. Posun obsahu smerom nadol v časti stránky, ktorú používateľ nevidí, nezhoršuje CLS, pretože sa meria len posun viditeľného obsahu vo viewporte. Jednoduchá, ale účinná taktika.
Oprava č. 4: Reklamy a embed obsah tretích strán
Reklamné sloty sú notoricky problematické pre CLS. Načítavajú sa asynchrónne, často s nepredvídateľnými rozmermi, a občas majú tendenciu úplne rozhodiť layout stránky. Tu je systematický prístup, ako ich skrotiť:
/* Kontajner pre reklamu s pevnými rozmermi */
.ad-slot {
min-height: 250px; /* Štandardná výška pre 300x250 reklamný formát */
min-width: 300px;
overflow: hidden;
contain: layout size style;
}
/* Fallback keď reklama nezaberá celý priestor */
.ad-slot:empty::before {
content: "";
display: block;
min-height: inherit;
}
Kľúčové pravidlá pre reklamy:
- Nikdy neumiestňujte dynamické reklamy na vrch stránky bez rezervovaného priestoru
- Používajte CSS vlastnosť
contain: layout size stylena izolovanie reklamného slotu - Pre embedded obsah (YouTube, Twitter, mapy) vždy definujte
widthaheightna elemente<iframe> - Zvážte implementáciu fasád — statických náhľadov, ktoré sa nahradia interaktívnym embedom až po interakcii používateľa
Oprava č. 5: CSS animácie a vlastnosť contain
CSS animácie, ktoré menia layout vlastnosti ako top, left, width, height, margin alebo padding, spôsobujú prepočet layoutu a môžu vyvolať CLS. Riešenie je našťastie jednoduché — animujte len composited vlastnosti.
Composited vs. layout vlastnosti
/* ZLÉ: animácia layout vlastností spôsobuje CLS */
.notification {
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from { top: -50px; }
to { top: 0; }
}
/* DOBRÉ: transform sa spracováva na compositor vlákne */
.notification {
animation: slideInFixed 0.3s ease-out;
}
@keyframes slideInFixed {
from { transform: translateY(-50px); }
to { transform: translateY(0); }
}
Bezpečné vlastnosti pre animácie (tie, čo nespôsobujú layout shift):
transform(translate, scale, rotate)opacityfilterclip-path
Ak si zapamätáte len tieto štyri, budete v pohode.
CSS contain pre izoláciu layoutu
Vlastnosť contain informuje prehliadač, že zmeny vnútri elementu neovplyvnia zvyšok stránky. To mu umožňuje výrazne optimalizovať prepočet layoutu:
/* Izoluje sidebar od hlavného obsahu */
.sidebar-widget {
contain: layout style;
}
/* Úplná izolácia pre nezávislé sekcie */
.comment-section {
contain: content; /* Skratka pre layout + style + paint */
}
/* Najagresívnejšia forma — aj veľkosť je izolovaná */
.ad-container {
contain: strict; /* Skratka pre size + layout + style + paint */
width: 300px;
height: 250px;
}
Oprava č. 6: content-visibility a contain-intrinsic-size
Pre dlhé stránky s množstvom obsahu pod viewportom (blogy, e-shopy s produktovými listami) ponúka CSS vlastnosť content-visibility: auto výrazné zlepšenie výkonu vykresľovania. Prehliadač jednoducho preskočí renderovanie obsahu, ktorý nie je vo viewporte.
Háčik je v tom, že ak neuvediete contain-intrinsic-size, element sa „zbalí" na nulovú výšku, čo spôsobí obrovský layout shift pri scrollovaní. A to naozaj nechcete.
/* Správna implementácia s contain-intrinsic-size */
.article-card {
content-visibility: auto;
contain-intrinsic-size: auto 400px;
/* auto = prehliadač si zapamätá skutočnú výšku po prvom renderovaní */
/* 400px = počiatočný odhad pred prvým renderovaním */
}
/* Pre grid/masonry layouty */
.product-grid-item {
content-visibility: auto;
contain-intrinsic-size: auto 350px;
}
/* Pre sekcie stránky */
.page-section {
content-visibility: auto;
contain-intrinsic-size: auto 600px;
}
Kľúčové slovo auto pred hodnotou rozmerov je tu naozaj dôležité — zabezpečí, že prehliadač si po prvom vykreslení elementu zapamätá jeho skutočné rozmery. Pri opakovanom scrollovaní hore a dolu tak nedochádza k skokom, pretože prehliadač už pozná reálnu výšku. Drobný detail, veľký rozdiel.
Debugging CLS: PerformanceObserver a Layout Shift Attribution API
Opraviť CLS bez presnej diagnostiky je ako hľadať ihlu v kope sena. A z vlastnej skúsenosti viem, že tipovanie „čo to asi spôsobuje" je nespoľahlivá stratégia. Našťastie, moderné prehliadačové API poskytujú detailné informácie o každom layout shifte.
Monitorovanie CLS pomocou PerformanceObserver
// Sledovanie všetkých layout shiftov na stránke
function observeLayoutShifts() {
let clsValue = 0;
let sessionValue = 0;
let sessionEntries = [];
const observer = new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
// Ignorovať shifty spôsobené interakciou používateľa
if (entry.hadRecentInput) continue;
// Aktualizovať session window
const lastEntry = sessionEntries[sessionEntries.length - 1];
if (
sessionEntries.length > 0 &&
entry.startTime - lastEntry.startTime < 1000 &&
entry.startTime - sessionEntries[0].startTime < 5000
) {
sessionValue += entry.value;
} else {
sessionValue = entry.value;
sessionEntries = [];
}
sessionEntries.push(entry);
if (sessionValue > clsValue) {
clsValue = sessionValue;
console.log("Nové max CLS:", clsValue.toFixed(4));
// Identifikácia vinníkov
for (const source of entry.sources || []) {
console.log(" Element:", source.node?.nodeName,
source.node?.className);
console.log(" Predtým:", source.previousRect);
console.log(" Potom:", source.currentRect);
}
}
}
});
observer.observe({ type: "layout-shift", buffered: true });
return observer;
}
observeLayoutShifts();
Identifikácia najhoršieho elementu v produkcii
Pre produkčné prostredie chcete zbierať CLS dáta od reálnych používateľov a identifikovať najčastejšie problematické elementy. Tu je snipper, ktorý sme úspešne nasadili na viacerých projektoch:
// Odoslanie CLS dát do analytics
function reportCLSToAnalytics() {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.hadRecentInput) continue;
const sources = entry.sources?.map((s) => ({
selector: getCSSSelector(s.node),
previousRect: rectToObj(s.previousRect),
currentRect: rectToObj(s.currentRect),
}));
// Odoslať len významné shifty
if (entry.value > 0.01) {
navigator.sendBeacon("/api/cls-report", JSON.stringify({
value: entry.value,
url: location.href,
sources,
timestamp: Date.now(),
}));
}
}
});
observer.observe({ type: "layout-shift", buffered: true });
}
function getCSSSelector(node) {
if (!node) return "unknown";
const parts = [];
while (node && node.nodeType === Node.ELEMENT_NODE) {
let selector = node.nodeName.toLowerCase();
if (node.id) {
selector += "#" + node.id;
parts.unshift(selector);
break;
}
if (node.className) {
selector += "." + [...node.classList].join(".");
}
parts.unshift(selector);
node = node.parentNode;
}
return parts.join(" > ");
}
function rectToObj(rect) {
return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
}
Debugging v Chrome DevTools
Pre lokálne debugovanie CLS v Chrome DevTools postupujte takto:
- Otvorte Performance panel a nahrajte stránku (F5 počas nahrávania)
- V sekcii Experience hľadajte fialové bloky označené „Layout Shift"
- Kliknite na konkrétny shift — v Summary paneli uvidíte skóre a posunuté elementy
- Zapnite Layout Shift Regions: Settings → More Tools → Rendering → Layout Shift Regions
- Obnovte stránku — oblasti postihnuté layout shiftom sa zvýraznia modrou farbou
Tip z praxe: Laboratórne nástroje merajú CLS len počas načítania stránky. V reálnom svete však používatelia scrollujú, interagujú a zostávajú na stránke oveľa dlhšie. Preto vždy uprednostnite field dáta z CrUX reportu alebo Google Search Console pred laboratórnymi meraniami. Viackrát sa mi stalo, že Lighthouse ukazoval perfektné CLS, no field dáta boli úplne iné.
Pokročilé techniky: View Transitions a CLS
V roku 2026 je View Transitions API podporované vo všetkých hlavných prehliadačoch (Chrome, Edge, Safari, Firefox). A tu je tá najlepšia správa: animácie vykonané cez View Transitions API nespôsobujú layout shifty, pretože prehliadač vytvára pseudo-elementy na compositor vrstve.
// Bezpečná zmena obsahu bez CLS
async function updateContent(newHTML) {
if (!document.startViewTransition) {
// Fallback pre staršie prehliadače
contentEl.innerHTML = newHTML;
return;
}
const transition = document.startViewTransition(() => {
contentEl.innerHTML = newHTML;
});
await transition.finished;
}
/* Definícia animácie prechodu */
::view-transition-old(content) {
animation: fadeOut 0.2s ease-out;
}
::view-transition-new(content) {
animation: fadeIn 0.3s ease-in;
}
/* Označenie elementu pre view transition */
.main-content {
view-transition-name: content;
}
View Transitions sú ideálne pre SPA navigáciu, prepínanie tabov, filtrovanie produktov a akúkoľvek zmenu obsahu, ktorá by inak spôsobila CLS. Ak ešte View Transitions nepoužívate, je najvyšší čas začať.
Kompletný checklist pre CLS pod 0,1
Na záver si zhrnieme všetky techniky do praktického checklistu. Môžete ho aplikovať na akýkoľvek web:
- Obrázky a médiá: Všetky
<img>,<video>a<iframe>majú atribútywidthaheight - Fonty: Fallback fonty používajú
size-adjusta metrické deskriptory; kritické fonty sú preloadované - Dynamický obsah: Kontajnery pre async obsah majú
min-height; bannery používajúposition: fixed/sticky - Reklamy: Reklamné sloty majú pevné rozmery a
contain: layout size style - Animácie: Všetky animácie používajú len
transformaopacity - Dlhý obsah: Sekcie pod viewportom používajú
content-visibility: autoscontain-intrinsic-size - Tretie strany: Externé widgety používajú fasády; nekritické skripty sú oneskorené
- Monitoring: Produkčný web má nasadený RUM monitoring CLS s identifikáciou zdrojov shiftov
Často kladené otázky (FAQ)
Čo je dobré CLS skóre a ako ho zmeriať?
Dobré CLS skóre je pod 0,1. Zmeriať ho môžete cez Google PageSpeed Insights (laboratórne aj field dáta), Lighthouse v Chrome DevTools, Google Search Console (Core Web Vitals report), alebo programaticky cez PerformanceObserver API. Pre najspoľahlivejšie výsledky používajte field dáta z CrUX reportu, pretože laboratórne merania zachytávajú CLS len počas načítania stránky.
Prečo mám zlé CLS skóre aj keď obrázky majú nastavené rozmery?
Obrázky sú len jednou z príčin CLS. Skontrolujte webové fonty (či používate size-adjust na fallback), dynamicky vkladaný obsah (cookie bannery, notifikácie), reklamy bez rezervovaného priestoru, CSS animácie používajúce layout vlastnosti a externé skripty tretích strán. Použite PerformanceObserver s Layout Shift Attribution API na identifikáciu presného elementu spôsobujúceho posun.
Ovplyvňuje CLS SEO a pozíciu vo vyhľadávaní Google?
Áno, a nie je to zanedbateľný vplyv. CLS je jednou z troch metrík Core Web Vitals, ktoré Google používa ako signál pri hodnotení stránok. Zlé CLS skóre môže negatívne ovplyvniť vaše pozície vo vyhľadávaní, najmä na mobilných zariadeniach. Okrem priameho vplyvu na SEO, zlá vizuálna stabilita zvyšuje bounce rate a znižuje konverzie — čo nepriamo ďalej zhoršuje výkon vo vyhľadávaní.
Ako opravím CLS spôsobený webovými fontami?
Najúčinnejšie riešenie v roku 2026 je kombinácia troch techník: (1) Definujte fallback font s metrickými deskriptormi size-adjust, ascent-override, descent-override a line-gap-override v @font-face pravidle. (2) Preloadujte kritické fonty cez <link rel="preload">. (3) Používajte font-display: swap na zobrazenie textu okamžite. Táto kombinácia minimalizuje vizuálny rozdiel pri výmene fontu takmer na nulu.
Počítajú sa do CLS aj layout shifty spôsobené scrollovaním?
Nie, a to je dobrá správa. CLS meria len neočakávané posuny obsahu. Layout shifty spôsobené scrollovaním (napr. sticky header, parallax efekty) sa do CLS nepočítajú, pokiaľ sú synchronné so scrollovaním. Rovnako sa nepočítajú shifty, ktoré nastanú do 500 ms po interakcii používateľa. CLS zaznamenáva len posuny, ktoré používateľ nečakal a ktoré nie sú výsledkom jeho akcie.