CLS na mobile: pięć źródeł przesunięć układu i jak je usunąć

3 listopada, 2026

Wynik CLS powyżej 0,1 na telefonach rzadko ma jedną przyczynę. W większości audytów, które robimy, kumulatywne przesunięcie układu składa się z kilku drobnych skoków: baner zgody spycha treść o 80 pikseli, font podmienia się i zmienia długość wiersza, reklama rozpycha akapit, a widżet ładowany leniwie wsuwa się nad tekst, który użytkownik właśnie czyta. Każdy z tych elementów osobno daje 0,02–0,06, razem przekraczają próg. Poniżej opisujemy pięć źródeł, które odpowiadają za zdecydowaną większość problemów, oraz sposób, by sprawdzić, że poprawka naprawdę zadziałała.

CLS jest jedną z trzech metryk Core Web Vitals, obok LCP i INP. Jeśli dopiero porządkujesz wydajność strony, zacznij od szerszego kontekstu w tekście Core Web Vitals 2026: INP w praktyce dla WordPressa, a potem wróć tutaj po konkretne poprawki układu.

Czym różni się CLS z laboratorium od CLS z danych polowych

Lighthouse i zakładka Performance w Chrome DevTools mierzą CLS tylko podczas ładowania strony, bez przewijania i bez interakcji. Dane polowe (Chrome UX Report, raport Core Web Vitals w Search Console) zbierają przesunięcia przez cały czas życia strony, a następnie wybierają najgorsze „okno sesji”: grupę przesunięć następujących po sobie w odstępach krótszych niż 1 sekunda, trwającą maksymalnie 5 sekund. Do oceny trafia 75. percentyl z 28 dni.

Stąd bierze się najczęstsze nieporozumienie: Lighthouse pokazuje 0,01, a Search Console zgłasza „Wymaga poprawy”. Przesunięcia, które powstają przy przewijaniu (reklama doładowana w połowie artykułu, komponent pojawiający się po dojechaniu do stopki), w teście laboratoryjnym po prostu nie występują. Szczegółową definicję metryki i sposób liczenia okien opisuje dokumentacja Cumulative Layout Shift na web.dev.

Warto też pamiętać o wyjątku: przesunięcia w ciągu 500 ms po kliknięciu, dotknięciu lub naciśnięciu klawisza nie są liczone, bo użytkownik się ich spodziewa. Rozwinięcie akordeonu po tapnięciu nie psuje wyniku. Ten sam akordeon otwierany automatycznie po 2 sekundach już tak.

Jak namierzyć konkretny element

Najszybsza metoda to biblioteka web-vitals z atrybucją (wersja attribution), która przy każdym raporcie CLS zwraca selektor elementu o największym przesunięciu. Wynik można wysłać jako zdarzenie do GA4 i po tygodniu mieć listę winowajców posortowaną według częstotliwości. Lokalnie wystarczy DevTools: w zakładce Performance włącz emulację telefonu i ograniczenie sieci, nagraj ładowanie wraz z przewinięciem, a ścieżka Layout Shifts pokaże każdy skok z podświetleniem elementu.

Baner zgody na cookies: najczęstsze źródło przesunięcia

Baner, który wstawia się na górze strony jako zwykły blok w przepływie dokumentu, przesuwa całą treść w dół o swoją wysokość. Na ekranie 390 pikseli szerokości taki baner ma często 150–250 pikseli, co samo w sobie daje CLS rzędu 0,15. Problem pogłębia się, gdy skrypt CMP ładuje się asynchronicznie i baner pojawia się dopiero po pierwszym wyrenderowaniu treści.

Rozwiązanie jest proste: baner powinien być nakładką z position: fixed przyklejoną do dołu ekranu albo modalem. Elementy pozycjonowane w ten sposób nie zmieniają układu pozostałej treści, więc nie generują przesunięć. Jeśli wdrażasz zgody przez Tag Managera, przy okazji sprawdź kolejność ładowania tagów opisaną w poradniku Consent Mode v2 w Google Tag Managerze krok po kroku, bo źle skonfigurowany CMP potrafi wstrzykiwać baner dwukrotnie.

Fonty webowe i skok tekstu po wczytaniu

Przeglądarka najpierw renderuje tekst fontem zastępczym (np. Arial), a po pobraniu fontu webowego podmienia go. Jeśli oba fonty mają inną szerokość znaków i wysokość linii, akapity zmieniają liczbę wierszy, a wszystko poniżej przesuwa się w górę lub w dół. Na mobile efekt jest silniejszy, bo wąska kolumna częściej łamie wiersze inaczej.

Skuteczne są trzy działania, najlepiej łącznie:

  • Preload jednego lub dwóch krytycznych plików fontu (krój podstawowy tekstu, ewentualnie nagłówków), żeby trafiły przed pierwszym renderowaniem.
  • Dopasowanie metryk fontu zastępczego deskryptorami size-adjust, ascent-override i descent-override w regule @font-face, tak by podmiana nie zmieniała wymiarów tekstu.
  • Rozważenie font-display: optional dla fontów dekoracyjnych: jeśli font nie zdąży się wczytać, strona zostaje przy zastępczym i nic nie skacze.

Konfigurację fontów lokalnie na WordPressie, bez ciężkich wtyczek, omawiamy w artykule WordPress i Core Web Vitals: cache, obrazy i fonty bez wtyczek all in one.

Obrazy i osadzenia bez zadeklarowanych wymiarów

Obraz bez atrybutów width i height zajmuje 0 pikseli wysokości do chwili pobrania. Gdy plik dociera, przeglądarka rozpycha układ. WordPress od lat dodaje wymiary do obrazów wstawianych z biblioteki mediów, ale luki zostają: obrazy w polach własnych motywu, grafiki w widżetach, logotypy w nagłówku wstawione przez CSS oraz obrazy z zewnętrznych CDN wklejane jako surowy HTML.

Jeszcze gorzej wypadają osadzenia: iframe z YouTube, posty z Instagrama czy X, mapy Google. Skrypt osadzenia często najpierw renderuje pusty kontener, a potem dopisuje treść o nieprzewidywalnej wysokości. Bezpieczny wzorzec to kontener z aspect-ratio (np. 16/9 dla wideo) albo z min-height odpowiadającą typowej wysokości osadzenia na mobile. Dla wideo dobrze sprawdza się też fasada: statyczna miniatura o znanych wymiarach, która ładuje odtwarzacz dopiero po kliknięciu. Przy okazji poprawia to LCP, co szerzej opisujemy w tekście o przyczynach LCP powyżej 4 sekund na mobile.

Reklamy i sloty o zmiennej wysokości

Sloty reklamowe to drugi po banerach zgody największy generator CLS w serwisach z monetyzacją. Problem polega na tym, że w momencie renderowania strony nie wiadomo, jaki format kreacji wygra aukcję: 300×250, 300×600 czy żaden. Jeśli kontener ma wysokość 0, każda wyświetlona reklama przesuwa treść.

Praktyka, która działa:

  • Zarezerwuj miejsce przez min-height równą najczęściej wyświetlanemu formatowi w danym slocie (dane znajdziesz w raportach Ad Managera).
  • Gdy reklama się nie wyświetli, nie zwijaj slotu do zera. Lepiej zostawić pusty, oznaczony obszar niż spowodować przesunięcie.
  • Unikaj slotów na samej górze artykułu; reklama nad treścią przesuwa wszystko, co jest pod nią.
  • Reklamy sticky (przyklejone do dołu ekranu) realizuj jako position: fixed, a nie jako blok dopisywany na końcu dokumentu.

Komponenty ładowane leniwie nad linią załamania

Leniwe ładowanie jest dobre dla wydajności, dopóki komponent nie wstawia się nad treścią, którą użytkownik już widzi. Typowe przykłady na WordPressie: pasek „powiązane artykuły” doładowywany przez AJAX pod tytułem, licznik udostępnień, który pojawia się po odpowiedzi API, belka z komunikatem o promocji wsuwana z góry po 3 sekundach, spis treści generowany przez JavaScript po załadowaniu strony.

Zasada jest jedna: każdy element, który pojawi się później, musi mieć zarezerwowane miejsce od pierwszego renderowania. W praktyce oznacza to szkielet (skeleton) o docelowej wysokości, renderowanie po stronie serwera tam, gdzie się da, oraz animacje oparte na transform, a nie na zmianie height czy top. Animacje transform nie zmieniają układu, więc nie są liczone do CLS. Jeśli komponent nie jest kluczowy, przenieś go pod treść główną, gdzie przesunięcie dotknie najwyżej stopki.

Jak potwierdzić poprawę w Search Console i CrUX

Po wdrożeniu poprawek dane polowe nie zmienią się od razu, bo CrUX liczy 28-dniowe okno kroczące. Pierwsze efekty zobaczysz po około tygodniu, pełny obraz po czterech tygodniach. Kolejność weryfikacji, którą stosujemy:

  1. Lokalnie: nagranie w DevTools z emulacją telefonu i przewinięciem całej strony, ścieżka Layout Shifts powinna być pusta lub mieć pojedyncze, drobne wpisy.
  2. Na produkcji: zdarzenia web-vitals w GA4 podzielone według typu urządzenia, porównanie 75. percentyla tydzień do tygodnia.
  3. W CrUX: zapytanie do CrUX API lub podgląd w PageSpeed Insights dla adresu i całego originu (dane pojawią się tylko przy wystarczającym ruchu).
  4. W Search Console: raport Podstawowe wskaźniki internetowe, grupa adresów z problemem CLS, przycisk „Sprawdź poprawkę”. Walidacja trwa 28 dni i obejmuje całą grupę podobnych adresów.

Jeśli po miesiącu grupa nadal ma status „Wymaga poprawy”, wróć do danych z atrybucją: zwykle zostaje jedno źródło, którego nie widać w laboratorium, najczęściej reklama lub widżet doładowywany przy przewijaniu.

FAQ

Jaki wynik CLS jest uznawany za dobry?

Dobry CLS to 0,1 lub mniej na 75. percentylu wizyt. Wartości od 0,1 do 0,25 wymagają poprawy, powyżej 0,25 wynik jest oceniany jako słaby. Progi są takie same dla mobile i desktopu, ale oceniane osobno.

Dlaczego Lighthouse pokazuje niski CLS, a Search Console wysoki?

Lighthouse mierzy tylko ładowanie strony bez przewijania i interakcji. Search Console korzysta z danych prawdziwych użytkowników, którzy przewijają stronę, więc widzi przesunięcia od reklam, leniwych komponentów i widżetów pojawiających się później.

Czy animacje na stronie zwiększają CLS?

Tylko te, które zmieniają układ, np. animowanie wysokości, marginesów lub pozycji top. Animacje oparte na transform i opacity nie przesuwają innych elementów i nie są liczone do CLS.

Ile trwa, zanim poprawa CLS będzie widoczna w Search Console?

Dane CrUX obejmują 28 dni, więc pierwsze zmiany widać po około tygodniu, a pełną poprawę po czterech tygodniach. Walidacja poprawki w Search Console również trwa do 28 dni.

Czy baner cookies zawsze psuje CLS?

Nie. Baner wyświetlany jako nakładka z pozycją fixed albo jako modal nie zmienia układu treści i nie generuje przesunięć. Problem pojawia się, gdy baner jest wstawiany jako zwykły blok na górze strony.