Sayfa kayması (CLS) nasıl sıfırlanır?

Okumaya başladığınız paragraf aşağı kayıyor, tıklamak üzere olduğunuz buton yerinden oynuyor ve yanlış linke basıyorsunuz. CLS bu deneyimi ölçüyor. İyi haber şu: üç Core Web Vitals metriği içinde düzeltmesi en öngörülebilir olan CLS, çünkü sebeplerin neredeyse tamamı birkaç kalıba giriyor.

Hedef, gerçek kullanıcıların 75. yüzdeliğinde CLS değerinin 0,1 veya altında olması. Ben çoğu sitede sıfıra yakın bir değeri makul bir hedef olarak görüyorum.

CLS neyi sayar, neyi saymaz?

CLS (Cumulative Layout Shift), görünür alandaki elemanların beklenmedik şekilde yer değiştirmesini ölçer. Her kayma için bir puan hesaplanır: kayan alanın görünür alana oranı ile kayma mesafesinin çarpımı. Bu puanlar "oturum pencereleri" (session windows) halinde gruplanır: aralarında 1 saniyeden az boşluk olan ve toplam 5 saniyeyi geçmeyen kaymalar aynı pencereye girer. Sayfanın CLS değeri, en yüksek puanlı penceredir.

CLS değeriDeğerlendirme
0,1 ve altıİyi
0,1-0,25İyileştirme gerekli
0,25 üstüKötü

Hariç tutulan durumlar önemli:

  • Kullanıcı girişinden sonraki 500 ms: Tıklama, dokunma veya tuş vuruşundan hemen sonra olan kaymalar beklenen kabul edilir. Akordeon açmak CLS üretmez.
  • Kaydırma: Sayfayı kaydırmak bir kayma değildir; elemanlar birbirine göre yer değiştirmez.
  • transform ile yapılan animasyonlar: Layout'u değiştirmediği için kayma sayılmaz.

Dikkat: "üzerine gelme" (hover) bir kullanıcı girişi sayılmaz. Hover ile açılıp içeriği iten bir menü CLS üretebilir.

En sık görülen sebepler

Boyutsuz görseller ve iframe'ler

Tarayıcı görselin boyutunu dosya inene kadar bilmiyorsa, ona sıfır yükseklik ayırır. Görsel inince alttaki her şey aşağı iter. YouTube gibi gömülü iframe'lerde de durum aynı.

Yer ayrılmamış reklamlar ve gömülü içerikler

Reklam alanı boş bir div olarak başlar, reklam gelince genişler. Reklamın boyutu değişken olabildiği için bu sorun sinsi: bazı gösterimlerde kayma olur, bazılarında olmaz.

Sonradan eklenen bantlar ve çerez bildirimleri

Sayfanın üstüne JavaScript ile eklenen kampanya bandı, "uygulamayı indir" çubuğu veya belge akışına eklenen çerez bildirimi tüm içeriği aşağı iter. Bu, sahada en çok gördüğüm sebep.

Web fontlarının değişmesi

Yedek font ile metin çizilir, web fontu inince yerine geçer. İki fontun genişlik ve satır yüksekliği farklıysa satır sayısı değişir ve paragraf altındaki her şey kayar.

Layout özelliklerini canlandırmak

top, left, height, margin gibi özellikleri canlandıran animasyonlar her karede layout'u değiştirir ve kayma üretebilir.

Çözümler

Görsel ve iframe'lere boyut vermek

En basit ve en etkili düzeltme: her <img> ve <iframe> için width ve height yazmak. Modern tarayıcılar bu iki değerden en-boy oranını hesaplar ve CSS'te genişlik esnek olsa bile doğru yüksekliği önceden ayırır.

<!-- Tarayıcı 16:9 oranını buradan hesaplar -->
<img src="/img/grafik.webp" width="1200" height="675" alt="Aylık tıklama grafiği">
/* Görsel esnek genişlikte, oran korunur */
img { max-width: 100%; height: auto; }

/* Gömülü video için oran önceden ayrılır */
.video-kapsayici iframe { width: 100%; aspect-ratio: 16 / 9; height: auto; }

Görsel optimizasyonunun diğer tarafları için Görsel optimizasyonu: WebP, AVIF ve lazy load yazısına bakabilirsiniz.

Reklam ve widget alanlarına yer ayırmak

Reklam alanına en sık gelen boyut kadar min-height verin. Reklam gelmezse boşluk kalır; bu, kaymadan iyidir. Görünür alanın en üstüne dinamik reklam koymamak da ayrıca işe yarar.

/* Mobilde 250 px yüksekliğinde reklam bekleniyor */
.reklam-alani { min-height: 250px; }
@media (min-width: 768px) { .reklam-alani { min-height: 90px; } }

Bantları içeriği itmeyecek şekilde göstermek

Çerez bildirimi ve kampanya bantları için benim kuralım şu: ya sunucudan HTML içinde gelsin ve baştan yeri olsun, ya da position: fixed ile içeriğin üzerinde bir katman (overlay) olarak görünsün. Belge akışına sonradan eklenip içeriği iten bant kabul edilemez.

Font kaynaklı kaymayı azaltmak

Yedek fontu web fontuna benzetmek için @font-face içindeki size-adjust, ascent-override, descent-override ve line-gap-override tanımlayıcılarını kullanabilirsiniz. Aşağıdaki değerler örnektir; kendi fontunuz için hesaplamanız gerekir. Bazı framework'ler ve font araçları bu değerleri otomatik üretir.

/* Yedek font, web fontunun ölçülerine yaklaştırılır (değerler örnektir) */
@font-face {
  font-family: "Inter Yedek";
  src: local("Arial");
  size-adjust: 107%;
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
}
body { font-family: "Inter", "Inter Yedek", sans-serif; }

Kritik fontu önceden yüklemek (preload) ve font-display: optional kullanmak da kaymayı azaltır; optional, font geç gelirse o sayfa görüntülemesinde yedek fontta kalır.

Animasyonlarda transform kullanmak

Bir elemanı kaydırmak, büyütmek veya açmak için transform: translate() ve transform: scale() kullanın. Bunlar layout'u etkilemez ve genellikle daha akıcı çalışır.

Kaymaları bulmak

Önce Search Console'daki Önemli Web Verileri (Core Web Vitals) raporundan hangi URL gruplarının etkilendiğine bakıyorum. Sonra sorunu kendi tarayıcımda yeniden üretmeye çalışıyorum.

  • DevTools > Rendering sekmesi > Layout Shift Regions: Bu seçeneği açtığınızda kayan alanlar anlık olarak renkli vurgulanır.
  • DevTools > Performance paneli: Kayıt aldığınızda layout shift'ler zaman çizelgesinde ayrı bir izde görünür; güncel sürümlerdeki Insights bölümü kaymaya yol açan olası sebepleri (culprits) de listeliyor.
  • PerformanceObserver: Hangi düğümün kaydığını ve oturum penceresi değerini konsolda görmek için aşağıdaki kodu kullanıyorum.
cls-kaynaklar.js
// Kaymaları, kaynak elemanları ve en yüksek oturum penceresini loglar
let pencere = 0, enYuksek = 0, ilk = 0, son = 0;

new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.hadRecentInput) continue; // kullanıcı girişinden sonraki kaymalar sayılmaz

    // 1 s boşluk veya 5 s pencere sınırı aşılırsa yeni pencere başlar
    if (pencere && entry.startTime - son < 1000 && entry.startTime - ilk < 5000) {
      pencere += entry.value;
    } else {
      pencere = entry.value;
      ilk = entry.startTime;
    }
    son = entry.startTime;
    enYuksek = Math.max(enYuksek, pencere);

    console.log('Kayma:', entry.value.toFixed(4), '| CLS (şimdilik):', enYuksek.toFixed(4));
    for (const kaynak of entry.sources || []) {
      console.log('  Kayan eleman:', kaynak.node, kaynak.previousRect, '->', kaynak.currentRect);
    }
  }
}).observe({ type: 'layout-shift', buffered: true });

Konsolda node üzerine gelince eleman sayfada vurgulanır. Çoğu zaman kayan eleman sorunun kendisi değil, onun üstüne sonradan eklenen bir şeydir; kayan elemanın hemen üstündeki kardeşine bakın.

Gerçek kullanıcılardan veri toplamak için web-vitals kütüphanesinin attribution sürümündeki onCLS fonksiyonu, en büyük kaymaya yol açan elemanı raporlar. Aynı yaklaşımı INP için INP'yi düzeltmek yazısında anlattım.

Laboratuvar testinin bir sınırı var: Lighthouse sadece yükleme sırasındaki kaymaları görür. Kaydırırken yüklenen reklamlar veya birkaç saniye sonra açılan bantlar sahada CLS üretir ama laboratuvarda görünmeyebilir. Bu yüzden sayfayı gerçekten kaydırarak, mobil görünümde ve yavaş ağ ayarıyla test edin.

Kontrol listesi

  • Tüm <img> ve <iframe> elemanlarında width/height veya CSS aspect-ratio var.
  • Reklam ve widget alanlarına min-height ile yer ayrıldı.
  • Çerez bildirimi ve kampanya bantları içeriği itmiyor; ya baştan yerleri var ya da overlay.
  • Web fontları için yedek font ayarlandı veya font-display: optional değerlendirildi.
  • Animasyonlar transform ile yapılıyor, layout özellikleriyle değil.
  • Hover ile açılan ve içeriği iten eleman yok.
  • DevTools'ta Layout Shift Regions açıkken sayfa baştan sona kaydırılarak test edildi.
  • Saha verisi için PerformanceObserver veya web-vitals ile kaynak elemanlar toplanıyor.

Comments / questions

If you have a question about any topic or wish to contact me: Telegram or send an email to [email protected] Thanks!

Related pages