Cumulative Layout Shift (CLS) to jedna z trzech metryk Core Web Vitals, która mierzy stabilność wizualną strony. Niespodziewane przesunięcia elementów frustrują użytkowników i mogą negatywnie wpłynąć na pozycje w Google. Ten przewodnik pomoże Ci zidentyfikować i naprawić problemy z CLS.
Czym jest CLS?
CLS mierzy sumę wszystkich nieoczekiwanych przesunięć layoutu, które występują podczas całego cyklu życia strony. Przesunięcie jest “nieoczekiwane”, gdy element zmienia pozycję bez interakcji użytkownika (np. kliknięcia).
Jak obliczany jest CLS?
CLS = suma (impact fraction × distance fraction) dla każdego przesunięcia
- Impact fraction - procent viewportu zajmowany przez przesunięty element
- Distance fraction - odległość przesunięcia jako procent viewportu
Progi CLS
| Wynik | Ocena |
|---|---|
| ≤ 0.1 | Dobry (zielony) |
| 0.1 - 0.25 | Wymaga poprawy (pomarańczowy) |
| > 0.25 | Słaby (czerwony) |
Najczęstsze przyczyny CLS
1. Obrazy bez wymiarów
Gdy przeglądarka nie zna wymiarów obrazu, rezerwuje 0px wysokości, a po załadowaniu obrazu następuje przesunięcie.
<!-- Źle - brak wymiarów -->
<img src="photo.jpg" alt="Zdjęcie">
<!-- Dobrze - wymiary określone -->
<img src="photo.jpg" alt="Zdjęcie" width="800" height="600">
<!-- Dobrze - aspect-ratio w CSS -->
<img src="photo.jpg" alt="Zdjęcie" style="aspect-ratio: 4/3; width: 100%;">
2. Reklamy i embedy
Reklamy często ładują się z opóźnieniem i mają dynamiczną wysokość.
<!-- Rezerwuj miejsce dla reklamy -->
<div class="ad-container" style="min-height: 250px;">
<!-- Kod reklamowy -->
</div>
3. Dynamicznie wstrzykiwana treść
Banery cookie, powiadomienia push, toolbary - wszystko co pojawia się po załadowaniu strony.
/* Zamiast przesuwać treść, użyj overlay */
.cookie-banner {
position: fixed;
bottom: 0;
/* NIE używaj position: relative na górze strony */
}
4. Web fonty (FOUT/FOIT)
Zamiana fontu fallback na docelowy może zmienić rozmiar tekstu.
/* Dopasuj fallback do docelowego fontu */
@font-face {
font-family: 'Inter';
src: url('/fonts/inter.woff2') format('woff2');
font-display: swap;
/* Użyj size-adjust dla dopasowania */
size-adjust: 100%;
ascent-override: 90%;
descent-override: 20%;
line-gap-override: 0%;
}
/* Lub użyj font-display: optional */
@font-face {
font-family: 'Inter';
src: url('/fonts/inter.woff2') format('woff2');
font-display: optional; /* Nie pokazuj fallback jeśli font się nie załaduje szybko */
}
5. Animacje powodujące reflow
Animacje width, height, top, left powodują reflow i mogą wpływać na CLS.
/* Źle - animacja height */
.menu {
transition: height 0.3s;
}
/* Dobrze - animacja transform */
.menu {
transition: transform 0.3s;
transform-origin: top;
}
.menu.collapsed {
transform: scaleY(0);
}
Narzędzia do debugowania CLS
1. Chrome DevTools - Performance
- Otwórz DevTools (F12)
- Przejdź do zakładki Performance
- Zaznacz Web Vitals
- Naciśnij Ctrl+Shift+E (nagrywanie z przeładowaniem)
- Szukaj czerwonych znaczników Layout Shift
2. Chrome DevTools - Rendering
- Otwórz DevTools
- Naciśnij Ctrl+Shift+P
- Wpisz “Show Rendering”
- Zaznacz Layout Shift Regions
- Niebieskie prostokąty pokazują przesunięcia w czasie rzeczywistym
3. Web Vitals Extension
Rozszerzenie do Chrome pokazujące CLS, LCP i INP w czasie rzeczywistym: Web Vitals Extension
4. Uper SEO Auditor
Wtyczka Uper SEO Auditor pozwala szybko sprawdzić wyniki Core Web Vitals bezpośrednio na analizowanej stronie — w tym wartość CLS:

Uper wskazuje też konkretne elementy powodujące przesunięcia layoutu — na przykładzie vw.com widać footer i div z wartością CLS aż 0.44:

Kolejny przykład — mymotoworld.com z CLS 0.431, gdzie aż 11 elementów powoduje przesunięcia layoutu, w tym filtry, wrapper produktów i nagłówki:

Podobny problem na parts.vw.com — treść SEO (div.seoTextContent) powoduje przesunięcie o 0.36, a łącznie 10 elementów wpływa na niestabilność layoutu:

5. Lighthouse
# CLI
npx lighthouse https://example.com --only-categories=performance
# Lub w Chrome DevTools → Lighthouse
6. PageSpeed Insights
https://pagespeed.web.dev/ - pokazuje CLS z danych laboratoryjnych i rzeczywistych (CrUX).
7. Layout Instability API
Programowe wykrywanie przesunięć:
// Nasłuchuj na layout shifts
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
console.log('Layout shift detected:', entry);
console.log('Value:', entry.value);
console.log('Sources:', entry.sources);
// Pokaż które elementy się przesunęły
entry.sources?.forEach(source => {
console.log('Element:', source.node);
console.log('Previous rect:', source.previousRect);
console.log('Current rect:', source.currentRect);
});
}
}
});
observer.observe({ type: 'layout-shift', buffered: true });
8. web-vitals library
import { onCLS } from 'web-vitals';
onCLS(console.log, { reportAllChanges: true });
// { name: 'CLS', value: 0.15, rating: 'needs-improvement', entries: [...] }
Techniki naprawy CLS
1. Rezerwacja miejsca dla obrazów
Metoda 1: width i height atrybuty
<img src="hero.jpg" width="1200" height="600" alt="Hero">
Przeglądarka obliczy aspect ratio i zarezerwuje miejsce.
Metoda 2: aspect-ratio CSS
.hero-image {
aspect-ratio: 16 / 9;
width: 100%;
height: auto;
}
Metoda 3: Padding hack (legacy)
.image-container {
position: relative;
padding-bottom: 56.25%; /* 16:9 = 9/16 = 0.5625 */
}
.image-container img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
2. Rezerwacja miejsca dla reklam
.ad-slot {
min-height: 250px; /* Standardowa wysokość reklamy */
background: #f0f0f0; /* Placeholder */
}
/* Dla responsywnych reklam */
.ad-slot-responsive {
min-height: 100px;
}
@media (min-width: 768px) {
.ad-slot-responsive {
min-height: 250px;
}
}
3. Skeleton screens
Zamiast pustego miejsca, pokaż skeleton:
<div class="card skeleton">
<div class="skeleton-image"></div>
<div class="skeleton-text"></div>
<div class="skeleton-text short"></div>
</div>
<style>
.skeleton-image {
aspect-ratio: 16/9;
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; }
}
</style>
4. Fonty - minimalizacja FOUT
Preload fontów:
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
Font-display: optional:
@font-face {
font-family: 'Inter';
src: url('/fonts/inter.woff2') format('woff2');
font-display: optional;
}
Font matching z CSS Font Loading API:
document.fonts.ready.then(() => {
document.body.classList.add('fonts-loaded');
});
/* Przed załadowaniem fontów - fallback z dopasowanymi metrykami */
body {
font-family: 'Inter Fallback', sans-serif;
}
/* Po załadowaniu */
body.fonts-loaded {
font-family: 'Inter', sans-serif;
}
5. Dynamiczna treść - transform zamiast reflow
/* Źle - dodawanie elementu przesuwa resztę */
.notification {
position: relative;
}
/* Dobrze - overlay nie wpływa na layout */
.notification {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
}
/* Lub animacja z transform */
.notification {
position: fixed;
top: 0;
transform: translateY(-100%);
transition: transform 0.3s;
}
.notification.visible {
transform: translateY(0);
}
6. Lazy loading z placeholder
<div class="lazy-container" style="aspect-ratio: 16/9;">
<img
src="placeholder.jpg"
data-src="actual-image.jpg"
loading="lazy"
alt="Opis"
>
</div>
// Intersection Observer dla lazy loading
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
document.querySelectorAll('img[data-src]').forEach(img => {
observer.observe(img);
});
7. Unikanie document.write()
// Źle - document.write blokuje parser
document.write('<script src="ad.js"></script>');
// Dobrze - dynamiczne wstawianie
const script = document.createElement('script');
script.src = 'ad.js';
script.async = true;
document.body.appendChild(script);
Debugowanie konkretnych scenariuszy
Scenariusz 1: CLS z Google Fonts
Problem: Tekst zmienia rozmiar po załadowaniu fontu.
Diagnoza:
- Otwórz DevTools → Network
- Filtruj po “font”
- Sprawdź timing fontów vs FCP
Rozwiązanie:
<!-- Preconnect do Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Lub self-hosting -->
<link rel="preload" href="/fonts/roboto.woff2" as="font" type="font/woff2" crossorigin>
Scenariusz 2: CLS z lazy-loaded images
Problem: Obrazy poniżej fold powodują CLS podczas scrollowania.
Diagnoza:
- Włącz Layout Shift Regions w Rendering
- Scrolluj stronę
- Obserwuj niebieskie prostokąty
Rozwiązanie:
<!-- Zawsze określaj wymiary -->
<img
src="photo.jpg"
loading="lazy"
width="400"
height="300"
alt="Zdjęcie"
>
Scenariusz 3: CLS z dynamicznymi banerami
Problem: Baner cookie/notyfikacja przesuwa treść.
Diagnoza:
- Odśwież stronę
- Obserwuj moment pojawienia się banera
- Sprawdź czy treść się przesuwa
Rozwiązanie:
/* Baner na dole jako overlay */
.cookie-banner {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 9999;
}
/* Lub rezerwacja miejsca na górze */
.page-wrapper {
padding-top: 60px; /* Wysokość banera */
}
Scenariusz 4: CLS z iframes (embedy)
Problem: YouTube embed, mapy, widgety społecznościowe.
Rozwiązanie:
<div class="video-container" style="aspect-ratio: 16/9;">
<iframe
src="https://www.youtube.com/embed/VIDEO_ID"
loading="lazy"
style="width: 100%; height: 100%;"
frameborder="0"
allowfullscreen
></iframe>
</div>
Checklist naprawy CLS
Obrazy i media
- Wszystkie
<img>mają width i height - Używam aspect-ratio dla responsywnych obrazów
- Iframes mają zdefiniowane wymiary
- Video posters są załadowane
Fonty
- Fonty są preloadowane
- Używam font-display: swap lub optional
- Fallback font ma podobne metryki
Reklamy i embedy
- Sloty reklamowe mają min-height
- Embedy społecznościowe mają placeholder
- Lazy-loaded content ma zarezerwowane miejsce
Dynamiczna treść
- Banery używają position: fixed
- Notyfikacje nie przesuwają treści
- Animacje używają transform zamiast reflow
JavaScript
- Brak document.write()
- Dynamicznie dodawana treść ma placeholder
- Skeleton screens dla ładowanych komponentów
Podsumowanie
CLS to metryka, która bezpośrednio wpływa na doświadczenie użytkownika. Nieoczekiwane przesunięcia są frustrujące i mogą prowadzić do przypadkowych kliknięć — dlatego stabilność layoutu jest jednym z elementów ocenianych podczas audytu SXO. Kluczowe zasady:
- Zawsze określaj wymiary obrazów i mediów
- Rezerwuj miejsce dla dynamicznej treści
- Optymalizuj fonty - preload i font-display
- Używaj position: fixed dla overlayów
- Monitoruj CLS w rzeczywistych danych (CrUX)
Cel to CLS poniżej 0.1 dla 75% użytkowników. Regularne testowanie i debugowanie pomoże utrzymać stabilny layout.
CLS to jedna z wielu metryk wpływających na widoczność w Google - zobacz kompletny przewodnik po technologiach web i SEO, aby dowiedzieć się, jak wszystkie wskaźniki współgrają ze sobą.
Często zadawane pytania
Co powoduje przesunięcia layoutu (CLS) na stronie?
Najczęstsze przyczyny to: obrazy i iframe bez zdefiniowanych wymiarów, dynamicznie wstrzykiwane reklamy, późno ładowane czcionki webowe (FOUT) oraz elementy dodawane do DOM powyżej widocznej treści.
Jak znaleźć elementy odpowiedzialne za wysoki CLS?
Użyj Chrome DevTools: otwórz zakładkę Performance, nagraj sesję i szukaj zdarzeń "Layout Shift". Kliknięcie pokaże dokładny element, który się przesunął. Możesz też użyć Web Vitals extension do podglądu CLS w czasie rzeczywistym.
Jaki wynik CLS jest akceptowalny?
Google klasyfikuje CLS poniżej 0,1 jako dobry, 0,1-0,25 jako wymagający poprawy, a powyżej 0,25 jako słaby. Wynik mierzony jest na 75. percentylu danych rzeczywistych użytkowników (CrUX).
Jak naprawić CLS spowodowany przez reklamy?
Zarezerwuj stałą przestrzeń na reklamy za pomocą CSS min-height na kontenerze. Jeśli reklama się nie załaduje, użytkownik zobaczy puste miejsce zamiast przeskoku layoutu. Dla lazy-loaded reklam użyj aspect-ratio na kontenerze.
Źródła
-
web.dev - Cumulative Layout Shift (CLS) https://web.dev/articles/cls
-
web.dev - Optimize CLS https://web.dev/articles/optimize-cls
-
web.dev - Debug layout shifts https://web.dev/articles/debug-layout-shifts
-
Chrome Developers - Layout Instability API https://developer.chrome.com/docs/web-platform/layout-instability-api
-
MDN - CSS aspect-ratio https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio



