CLS Önleme: Görsel Kararlılık ve Düzen Değiştirme Hata Ayıklama
Bir makale okuyorsunuz. İlginç bir bağlantıya tıklamak üzeresiniz. Aniden içerik 200 piksel aşağı tıklanır: bir reklam banner'ı görünür yukarıda. Yanlış bağlantıya tıkladınız. Bu fenomen – düzen değişikliği - ve tam olarak ne Kümülatif Düzen Kayması (CLS) ölçüm. Google, bunu kullanıcı deneyimi açısından en önemli üç Önemli Web Verisinden biri olarak görüyor.
0,1'in altındaki bir CLS "iyi" olarak kabul edilir; 0,1 ile 0,25 arasında "iyileştirme"; 0,25'in üzerinde "kıt". İyi haber: LCP'den farklı olarak (ağ optimizasyonları gerektirir) ve sunucu), CLS neredeyse tamamen CSS ve HTML'ye bağlıdır. Doğru tekniklerle CLS'yi sıfıra getirebilirsiniz.
Ne Öğreneceksiniz
- Tarayıcı CLS'yi nasıl hesaplar: etki oranı, mesafe oranı, puan
- Chrome DevTools'taki Düzen Değiştirme Bölgeleri ile CLS'nin nedenlerini belirleyin
- Boyutsuz görseller: bir numaralı suçlu
- en boy oranlı CSS: resim yüklenmeden önce yer ayırın
- CLS yazı tipi değişimi: geri dönüş ölçümleri için yazı tipi görüntüleme ve boyut ayarlama
- Dinamik olarak eklenen içerik: banner reklamlar, çerez izni, iskelet ekranlar
- CLS'ye neden olan animasyonlar: dönüşüm ve üst/sol
CLS Nasıl Hesaplanır?
CLS, kaç vardiyanın meydana geldiğini değil, bunların kullanıcıyı ne kadar "rahatsız ettiğini" ölçer. Her düzen değişikliği şu şekilde hesaplanan bir puan üretir:
düzen değişikliği puanı = etki oranı × mesafe oranı
L'darbe oranı ve "etkilenen" görüntü alanının yüzdesi vardiyadan (daha önce işgal edilen alan + sonra işgal edilen alan). Orada mesafe kesri ve etkilenen herhangi bir öğenin kesir olarak kat ettiği maksimum mesafe görünüm alanı boyutunda. Son CLS tüm vardiyaların toplamıdır maksimum 5 saniyelik oturum pencerelerinde meydana geldi.
// Misura il CLS con PerformanceObserver
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Ignora shift causati dall'interazione utente (scroll, click)
// Gli shift da input utente non vengono penalizzati
if (!entry.hadRecentInput) {
console.log('Layout Shift detected:', {
score: entry.value.toFixed(4),
time: Math.round(entry.startTime),
sources: entry.sources?.map((source) => ({
element: source.node?.tagName,
elementId: source.node?.id,
previousRect: source.previousRect,
currentRect: source.currentRect,
})),
});
}
}
});
observer.observe({ type: 'layout-shift', buffered: true });
// Calcola il CLS cumulativo (sessione finestra da 5s)
let clsScore = 0;
let sessionValue = 0;
let sessionEntries: PerformanceEntry[] = [];
const clsObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
const firstEntry = sessionEntries[0];
const lastEntry = sessionEntries[sessionEntries.length - 1];
if (
sessionEntries.length === 0 ||
entry.startTime - lastEntry.startTime < 1000 &&
entry.startTime - firstEntry.startTime < 5000
) {
sessionEntries.push(entry);
sessionValue += entry.value;
} else {
clsScore = Math.max(clsScore, sessionValue);
sessionEntries = [entry];
sessionValue = entry.value;
}
}
}
clsScore = Math.max(clsScore, sessionValue);
console.log('Current CLS:', clsScore.toFixed(4));
});
clsObserver.observe({ type: 'layout-shift', buffered: true });
Boyutsuz Görüntüler: Ana Suçlu
Yüksek CLS'nin en yaygın durumu: niteliksiz görüntüler width
e height. Tarayıcı resim için ne kadar alan ayrılacağını bilmiyor
yüklenmeden önce metin oluşturulur ve alanı kaplar,
daha sonra görüntü geldiğinde her şeyi aşağı iter.
/* CSS: l'aspect-ratio e implicito dagli attributi HTML */
img {
width: 100%; /* si adatta al container */
height: auto; /* mantiene l'aspect-ratio automaticamente */
/* Il browser usa width/height HTML per calcolare lo spazio da riservare */
}
/* aspect-ratio CSS: riserva spazio per qualsiasi contenuto */
/* Contenitore immagine con aspect-ratio 16:9 */
.image-container {
width: 100%;
aspect-ratio: 16 / 9; /* Riserva lo spazio prima del caricamento */
background: #f0f0f0; /* Placeholder visivo */
overflow: hidden;
}
.image-container img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* Per contenuto embedded (iframe, video, mappe) */
.video-wrapper {
position: relative;
width: 100%;
aspect-ratio: 16 / 9;
background: #000;
}
.video-wrapper iframe,
.video-wrapper video {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
/* Skeleton screen: placeholder animato che riserva lo spazio corretto */
.skeleton {
aspect-ratio: 16 / 9;
background: linear-gradient(
90deg,
#f0f0f0 25%,
#e0e0e0 50%,
#f0f0f0 75%
);
background-size: 200% 100%;
animation: skeleton-loading 1.5s infinite;
}
@keyframes skeleton-loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
Yazı Tipi Değiştirme CLS: boyut ayarlama ve Geri Dönüş Metrikleri
Bir web yazı tipi yüklendiğinde ve sistem yazı tipinin yerini aldığında, metin
boyutu değiştirebilir ve düzenin değişmesine neden olabilir. size-adjust,
ascent-override, descent-override e line-gap-override
azaltmak veya ortadan kaldırmak için geri dönüş yazı tipi ölçümlerini kalibre etmenize olanak tanır
bu değişim.
/* Minimizzare il CLS da font swap con @font-face overrides */
/* Passo 1: misura le metriche del tuo web font */
/* Usa: https://screenspan.net/fallback o il Chrome DevTools > Fonts */
/* Passo 2: crea un @font-face per il fallback calibrato */
@font-face {
font-family: 'Inter-Fallback';
src: local('Arial'); /* Font di sistema disponibile ovunque */
/* Valori per avvicinare Arial alle metriche di Inter */
/* (calcolati con fontaine o screenspan.net) */
ascent-override: 90.20%;
descent-override: 22.48%;
line-gap-override: 0%;
size-adjust: 107.40%;
}
/* Passo 3: usa il fallback nella font-family stack */
body {
font-family: 'Inter', 'Inter-Fallback', system-ui, sans-serif;
}
/* Risultato: quando Inter caricherà, il layout quasi non cambierà
perché Arial è stato ridimensionato per avere le stesse metriche */
/* font-display: optional per zero FOIT e CLS minimo */
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-400.woff2') format('woff2');
font-weight: 400;
font-display: optional;
/* Se il font non è in cache al primo caricamento:
usa il fallback per sempre (zero swap = zero CLS)
Dal secondo caricamento: font in cache, usato immediatamente */
}
Dinamik Olarak Yerleştirilen İçerik
Reklam banner'ları, çerez izni, anlık bildirimler, getirme yoluyla yüklenen içerik: İlk oluşturma işleminden sonra DOM'a eklenen tüm içerik önceden yer ayırtılmazsa düzenin değişmesine neden olur.
/* Riserva spazio per banner pubblicitari e cookie consent */
/* Banner pubblicitari: usa min-height per riservare spazio */
.ad-slot {
min-height: 90px; /* Dimensione standard banner leaderboard */
width: 100%;
display: flex;
align-items: center;
justify-content: center;
background: #f5f5f5;
}
/* Cookie consent banner: posiziona in modo non-intrusivo */
.cookie-consent {
position: fixed; /* fixed non causa layout shift sul contenuto */
bottom: 0;
left: 0;
right: 0;
z-index: 9999;
/* Non usa position: sticky o relative che potrebbero shiftare il contenuto */
}
/* Contenuto lazy-loaded: schermata skeleton con dimensione fissa */
.lazy-content {
min-height: 200px; /* Riserva spazio prima del caricamento */
}
.lazy-content.loaded {
min-height: auto; /* Rimuovi dopo il caricamento se il contenuto è più alto */
}
/* SBAGLIATO: inserire contenuto in alto nella pagina */
/* Evita di aggiungere elementi PRIMA del contenuto gia visibile */
/* CORRETTO: usa position: fixed o bottom per contenuto dinamico */
.notification-bar {
position: fixed; /* Non sposta il contenuto esistente */
top: 0;
left: 0;
right: 0;
}
CLS'ye Neden Olan Animasyonlar
Tüm animasyonlar CLS'ye neden olmaz. Kullandıkları animasyonlar transform
e opacity Besteci iş parçacığında meydana gelirler ve düzen değişikliklerine neden olmazlar.
Değişen animasyonlar top, left, width,
height, margin, padding yeniden akışa neden olurlar
ve kullanıcı etkileşimi olmadan meydana gelmeleri durumunda CLS'ye katkıda bulunabilir.
/* Animazioni CLS-safe: usa solo transform e opacity */
/* SBAGLIATO: anima proprieta che causano reflow */
.slide-in-bad {
animation: slideInBad 0.3s ease;
}
@keyframes slideInBad {
from { top: -100px; } /* causa layout reflow */
to { top: 0; }
}
/* CORRETTO: usa transform (compositor-only, nessun layout reflow) */
.slide-in-good {
animation: slideInGood 0.3s ease;
}
@keyframes slideInGood {
from { transform: translateY(-100px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
/* Accordion / Expand-Collapse: usa max-height con attenzione */
/* max-height puo causare CLS se l'elemento e above-the-fold */
.accordion-content {
overflow: hidden;
/* Usa Grid per animazioni smooth senza max-height */
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 0.3s ease;
}
.accordion-content.open {
grid-template-rows: 1fr;
}
.accordion-content > div {
overflow: hidden;
}
/* Tooltip e overlay: position absolute/fixed mai relative */
.tooltip {
position: absolute; /* Non sposta il contenuto circostante */
z-index: 1000;
pointer-events: none;
}
/* Usa will-change con parsimonia per ottimizzare animazioni pesanti */
.heavy-animation {
will-change: transform;
/* Solo se l'animazione e effettivamente pesante,
will-change crea un nuovo layer che usa piu memoria */
}
Chrome DevTools ile CLS'de hata ayıklama
Chrome DevTools, sorunun kesin nedenini belirlemek için özel araçlar sunar her düzen değişikliğinde. En etkili yöntem:
// Step-by-step: debug CLS in Chrome DevTools
// 1. Apri DevTools > Performance
// 2. Clicca "Record" e ricarica la pagina
// 3. Ferma la registrazione dopo il caricamento completo
// 4. Nella timeline, cerca il marker "Layout Shift" (barra rossa)
// 5. Clicca su un Layout Shift entry per vedere:
// - "Sources": quali elementi si sono spostati
// - "Score": il contributo di questo shift al CLS
// - "Had Recent Input": se causato da interazione utente (non conta)
// 6. Usa "Layout Shift Regions" checkbox nella barra superiore
// per visualizzare in overlay gli elementi che si spostano
// Oppure usa questa snippet nella Console:
document.addEventListener('DOMContentLoaded', () => {
const obs = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput && entry.value > 0) {
const elements = entry.sources
?.map((s) => s.node)
.filter(Boolean) ?? [];
// Evidenzia in rosso gli elementi che causano shift
elements.forEach((el) => {
if (el instanceof HTMLElement) {
el.style.outline = '3px solid red';
el.title = `CLS: ${entry.value.toFixed(4)}`;
setTimeout(() => {
el.style.outline = '';
}, 3000);
}
});
console.warn('CLS shift:', entry.value.toFixed(4), elements);
}
}
});
obs.observe({ type: 'layout-shift', buffered: true });
});
CLS Kontrol Listesi: Sıfır Düzen Kayması
- Tüm görsellerin genişlik ve yükseklik özellikleri (veya CSS aracılığıyla en boy oranı) vardır
- Yazı tipleri font-display'i kullanır: takas veya kalibre edilmiş geri dönüş ile isteğe bağlı
- Banner reklamlarda minimum yükseklikte ayrılmış alan bulunur
- Çerez izni ve bildirim kullanım konumu: sabit
- Animasyonlar yalnızca dönüştürme ve opaklık kullanır (üst/sol/genişlik değil)
- Geç yüklenen içeriğin eşdeğer boyutta bir yer tutucusu var
- Mobil cihazda Chrome DevTools'ta CLS'yi kontrol edin (4G emülasyonu)
Sonuçlar
CLS, LCP'den farklı olarak en kontrol edilebilir Önemli Web Verileri ölçümüdür (ağa ve sunucuya bağlıdır) ve INP (karmaşıklığa bağlıdır) JavaScript), CLS neredeyse tamamen CSS ve HTML seçimlerine bağlıdır. Boyutlarla resimlerde açık, optimize edilmiş yazı tipi gösterimi ve yönetilen dinamik içerik konum sabitlendiğinde CLS'yi her türlü sitede sıfıra getirebilirsiniz.







