---
title: "CLS Debugging: Jak wykryć i naprawić Cumulative Layout Shift"
description: "Kompletny przewodnik po debugowaniu CLS. Poznaj przyczyny przesunięć layoutu, narzędzia do wykrywania problemów i techniki naprawy Core Web Vitals."
date: 2025-12-20
updated: 2026-03-16
category: Optymalizacja
tags: ["Core Web Vitals", "CLS", "Performance", "SEO", "Optymalizacja", "Layout Shift"]
url: https://uper.pl/blog/cls-debugging/
---

# CLS Debugging: Jak wykryć i naprawić Cumulative Layout Shift

**Cumulative Layout Shift (CLS)** to jedna z trzech metryk [Core Web Vitals](/blog/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.

```html
<!-- Ź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ść.

```html
<!-- 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.

```css
/* 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.

```css
/* 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.

```css
/* Ź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

1. Otwórz DevTools (F12)
2. Przejdź do zakładki **Performance**
3. Zaznacz **Web Vitals**
4. Naciśnij **Ctrl+Shift+E** (nagrywanie z przeładowaniem)
5. Szukaj czerwonych znaczników **Layout Shift**

### 2. Chrome DevTools - Rendering

1. Otwórz DevTools
2. Naciśnij **Ctrl+Shift+P**
3. Wpisz "Show Rendering"
4. Zaznacz **Layout Shift Regions**
5. 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](https://chrome.google.com/webstore/detail/web-vitals/ahfhijdlegdabablpippeagghigmibma)

### 4. Uper SEO Auditor

Wtyczka [Uper SEO Auditor](https://chromewebstore.google.com/detail/uper-seo-auditor/khhpbeckpphaoiemjdijhbfpjnendage) pozwala szybko sprawdzić wyniki Core Web Vitals bezpośrednio na analizowanej stronie — w tym wartość CLS:

![Core Web Vitals w Uper SEO Auditor — LCP 124ms, CLS 0.032, INP 8ms](/blog/uper-core-web-vitals.jpg)

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:

![Problemy CLS wykryte przez Uper SEO Auditor na vw.com — footer i div z CLS 0.44](/blog/vw-com-core-web-vitals-cls-issues.png)

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:

![Problemy CLS wykryte przez Uper SEO Auditor na mymotoworld.com — CLS 0.431 z licznymi źródłami przesunięć](/blog/mymotoworld-com-cls-issues.png)

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:

![Problemy CLS wykryte przez Uper SEO Auditor na parts.vw.com — dynamicznie ładowana treść SEO jako główne źródło przesunięć](/blog/parts-vw-com-cls-issues.png)

### 5. Lighthouse

```bash
# CLI
npx lighthouse https://example.com --only-categories=performance

# Lub w Chrome DevTools → Lighthouse
```

### 6. PageSpeed Insights

[https://pagespeed.web.dev/](https://pagespeed.web.dev/) - pokazuje CLS z danych laboratoryjnych i rzeczywistych (CrUX).

### 7. Layout Instability API

Programowe wykrywanie przesunięć:

```javascript
// 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

```javascript

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**

```html
<img src="hero.jpg" width="1200" height="600" alt="Hero">
```

Przeglądarka obliczy aspect ratio i zarezerwuje miejsce.

**Metoda 2: aspect-ratio CSS**

```css
.hero-image {
  aspect-ratio: 16 / 9;
  width: 100%;
  height: auto;
}
```

**Metoda 3: Padding hack (legacy)**

```css
.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

```css
.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:

```html
<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:**

```html
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
```

**Font-display: optional:**

```css
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter.woff2') format('woff2');
  font-display: optional;
}
```

**Font matching z CSS Font Loading API:**

```javascript
document.fonts.ready.then(() => {
  document.body.classList.add('fonts-loaded');
});
```

```css
/* 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

```css
/* Ź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

```html
<div class="lazy-container" style="aspect-ratio: 16/9;">
  <img
    src="placeholder.jpg"
    data-src="actual-image.jpg"
    loading="lazy"
    alt="Opis"
  >
</div>
```

```javascript
// 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()

```javascript
// Ź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:**
1. Otwórz DevTools → Network
2. Filtruj po "font"
3. Sprawdź timing fontów vs FCP

**Rozwiązanie:**
```html
<!-- 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:**
1. Włącz Layout Shift Regions w Rendering
2. Scrolluj stronę
3. Obserwuj niebieskie prostokąty

**Rozwiązanie:**
```html
<!-- 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:**
1. Odśwież stronę
2. Obserwuj moment pojawienia się banera
3. Sprawdź czy treść się przesuwa

**Rozwiązanie:**
```css
/* 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:**
```html
<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](/audyt-sxo/). Kluczowe zasady:

1. **Zawsze określaj wymiary** obrazów i mediów
2. **Rezerwuj miejsce** dla dynamicznej treści
3. **Optymalizuj fonty** - preload i font-display
4. **Używaj position: fixed** dla overlayów
5. **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](/blog/technologie-web-seo-ranking-google/), aby dowiedzieć się, jak wszystkie wskaźniki współgrają ze sobą.

<FaqBlog
  questions={[
    {
      question: 'Co powoduje przesunięcia layoutu (CLS) na stronie?',
      answer: '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.'
    },
    {
      question: 'Jak znaleźć elementy odpowiedzialne za wysoki CLS?',
      answer: 'Użyj Chrome DevTools: otwórz zakładkę <strong>Performance</strong>, nagraj sesję i szukaj zdarzeń "Layout Shift". Kliknięcie pokaże dokładny element, który się przesunął. Możesz też użyć <strong>Web Vitals extension</strong> do podglądu CLS w czasie rzeczywistym.'
    },
    {
      question: 'Jaki wynik CLS jest akceptowalny?',
      answer: 'Google klasyfikuje CLS <strong>poniżej 0,1</strong> 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).'
    },
    {
      question: 'Jak naprawić CLS spowodowany przez reklamy?',
      answer: 'Zarezerwuj stałą przestrzeń na reklamy za pomocą CSS <strong>min-height</strong> na kontenerze. Jeśli reklama się nie załaduje, użytkownik zobaczy puste miejsce zamiast przeskoku layoutu. Dla lazy-loaded reklam użyj <strong>aspect-ratio</strong> na kontenerze.'
    }
  ]}
  heading="Często zadawane pytania"
  id="faq"
/>

## Źródła

1. **web.dev - Cumulative Layout Shift (CLS)**
[https://web.dev/articles/cls](https://web.dev/articles/cls)

2. **web.dev - Optimize CLS**
[https://web.dev/articles/optimize-cls](https://web.dev/articles/optimize-cls)

3. **web.dev - Debug layout shifts**
[https://web.dev/articles/debug-layout-shifts](https://web.dev/articles/debug-layout-shifts)

4. **Chrome Developers - Layout Instability API**
[https://developer.chrome.com/docs/web-platform/layout-instability-api](https://developer.chrome.com/docs/web-platform/layout-instability-api)

5. **MDN - CSS aspect-ratio**
[https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio](https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio)
