Ana Sayfa / Blog / Lazy loading: native vs IntersectionObserver karşılaştırma

Lazy loading: native vs IntersectionObserver karşılaştırma

Native loading=lazy modern browser'larda yeterli mi yoksa IntersectionObserver custom implementation hala gerekli mi? 3 projeden pratik karşılaştırma.

Lazy loading 5 yıl önce “custom JS yazmalıyım” iken bugün loading="lazy" attribute ile tek satır. Ama çoğu projede hala IntersectionObserver ile custom lazy loading görüyorum. Gerçekten gerekli mi?

Bu yazıda 3 farklı projede yaptığım karşılaştırma.

Native loading=”lazy”: ne sağlıyor

Chrome 77, Firefox 75, Safari 15.4 itibariyle native destekleniyor. 2026’da browser support ~%95+.

Kullanımı:

<img src="..." loading="lazy" alt="...">
<iframe src="..." loading="lazy"></iframe>

Browser viewport’a yakın olana kadar resource’u yüklemiyor. Görsel fold altında ise HTTP request’i yapılmıyor.

Native’in avantajları

Sıfır JavaScript. Attribute eklemek yeterli. JS bundle boyutu artmıyor.

Browser optimize. Browser kendi priority signal’larını kullanıyor (connection speed, data saver mode, viewport distance). JS ile yapıldığında bu heuristic’lere ulaşamıyorsunuz.

Otomatik decode optimization. Browser off-main-thread decode yapıyor, main thread bloklamıyor.

Performance overhead yok. Custom IntersectionObserver yüz elementi izlerken CPU tuketir. Native native.

Native’in sınırlamaları

Threshold customize edilemiyor. Browser kendi kararını veriyor “ne kadar yaklaşınca yükleyeceğim?”. Chrome genelde viewport’tan 1250px önce yüklüyor (mobile’da 2500px). Bazı case’lerde çok erken, bazı case’lerde çok geç.

Sadece img ve iframe. div background image, video poster, style.backgroundImage lazy değil. Custom element lazy lazy olmuyor.

Prerender/prefetch’le etkileşim karmaşık. Speculative parsing ile etkileşimi edge case’lerde garip davranabiliyor.

Callback yok. Element yüklendiğinde “şimdi yüklendi” event’i yakalamak için load listener ayrı eklemelisiniz. Fade-in animation için ekstra iş.

IntersectionObserver: ne zaman gerekli

Custom implementation şu senaryolarda hala gerekli:

Div background image. Native support yok.

const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            const div = entry.target;
            div.style.backgroundImage = `url(${div.dataset.bg})`;
            observer.unobserve(div);
        }
    });
}, {rootMargin: '50px'});

document.querySelectorAll('[data-bg]').forEach(el => observer.observe(el));

Video autoplay near viewport. poster lazy native, ama autoplay’i viewport yakın olunca tetiklemek istiyorsanız JS.

Lazy component render. Bir React component’i fold altında mı, sadece yaklaşınca render etmek? Native çözmüyor, IntersectionObserver + lazy() kombinasyonu.

Analytics event on viewport. “Bu banner gerçekten görüldü mü?” attribution için IntersectionObserver zaten kullanılıyor, lazy loading’i entegre etmek basit.

Custom threshold. “Viewport’a 200px kala yükle” gibi spesifik kontrol gerekiyorsa native yetmiyor. IntersectionObserver rootMargin ile tam kontrol.

Intersection animation. Fade-in görsel animasyonu için yüklenme event’inden sonra yüklenme tetiklemek: IntersectionObserver + load listener’ı tek yerde yönetmek iyi.

3 proje karşılaştırması

Proje 1: E-ticaret ürün listesi. 200 ürün kartı, her biri 1 görsel. Native loading="lazy" yetiyor. Custom JS eklemek sadece bundle size arttırıyor. Tercih: native.

Proje 2: Portfolio galerisi. Lightbox’lı high-res görseller, div background image kullanılan section’lar. Native + IntersectionObserver hybrid. Img’lerde native, background’larda IO.

Proje 3: Blog. Long-form article, görsel az. Native bile gerekli değil, sayfa ağırlığı az, eager serve daha iyi LCP veriyor.

Her projede tek bir answer yok. Duruma bakmak gerekiyor.

Hybrid pattern

Hem native hem custom birarada şu şekilde iyi çalışıyor:

<!-- Native lazy for images -->
<img src="..." loading="lazy" class="lazy-fade" alt="...">

<!-- Custom for background images -->
<div data-bg="/path/to/bg.jpg" class="lazy-bg"></div>

JS tarafı:

// Fade-in for natively lazy images
document.querySelectorAll('.lazy-fade').forEach(img => {
    if (img.complete) {
        img.classList.add('loaded');
    } else {
        img.addEventListener('load', () => img.classList.add('loaded'));
    }
});

// IntersectionObserver for background images
const bgObserver = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            const el = entry.target;
            el.style.backgroundImage = `url(${el.dataset.bg})`;
            el.classList.add('loaded');
            bgObserver.unobserve(el);
        }
    });
}, {rootMargin: '100px'});

document.querySelectorAll('.lazy-bg').forEach(el => bgObserver.observe(el));

Bu pattern 2 KB JS, net kazanç.

Performance measurement

Lazy loading etkisini ölçmek için:

LCP: fold üstü görsellerin hızlı yüklendiğini doğrulayın. Lazy loading LCP görselinde kullanılmamalı.

FCP: genel olarak etkilenmemeli.

Total bandwidth: DevTools Network panel’de toplam transfer size. Lazy loading ile fark açılıyor, uzun scroll gerektirmeden sayfa hafif.

CLS: width/height explicit yoksa lazy loading CLS’i kötüleştirebilir. Placeholder dimension’ı belirli olsun.

Yaygın hata: above-the-fold lazy

Gördüğüm en sık hata: hero image’a da loading="lazy" eklemek. Browser priority heuristic ile yine yüklüyor ama kararsız kalıyor, LCP 200-400ms kötüleşiyor.

Fold üstü elementleri eager, hatta fetchpriority="high" ile priority yüksek tutun.

<!-- LCP image -->
<img src="hero.jpg" fetchpriority="high" alt="...">

<!-- Below fold -->
<img src="gallery-1.jpg" loading="lazy" alt="...">

Fetch priority: yeni bir lever

Fetchpriority attribute (Chrome 101+, Safari 17.2+) native lazy loading’in sonraki adımı. High / low / auto değerler alıyor.

  • fetchpriority="high": LCP, above-fold
  • fetchpriority="low": decorative, absolutely not critical
  • fetchpriority="auto": default

Browser bu sinyalleri kullanarak request’leri önceliklendiriyor. LCP improvement için güçlü bir lever.

Karar rehberim

  1. Proje small/medium ise + sadece img/iframe lazy gerekli: native sadece
  2. Background image / custom element lazy gerekli: IntersectionObserver
  3. Her ikisi de: hybrid pattern
  4. Below-fold images çoğunlukta, above-fold az: kesinlikle lazy loading
  5. Above-fold images çoğunlukta: lazy gerekmeyebilir, eager + fetchpriority

Son not

2020’den önce custom JS zorunlu idi. Bugün native 80% use case’i çözüyor. IntersectionObserver’ı ben hala kullanıyorum ama sadece native’in yetmediği yerlerde.

Yeni projede önce native attribute’u dener, ihtiyaç çıktıkça custom JS ekleriz. Kod minimal, performance max.

Bu konuda bir projeniz mi var?

Kısa bir özet bırakın, 24 saat içinde size dönüş yapayım.

İletişime Geç