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. Lighthouse
# CLI
npx lighthouse https://example.com --only-categories=performance
# Lub w Chrome DevTools → Lighthouse
5. PageSpeed Insights
https://pagespeed.web.dev/ - pokazuje CLS z danych laboratoryjnych i rzeczywistych (CrUX).
6. 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 });
7. 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ęć. 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.
Ź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



