Lazy loading artinya menunda mengunduh sesuatu sampai benar-benar mau dilihat. Gambar dan iframe yang jauh di bawah layar pertama gak ikut diunduh waktu halaman dibuka, tapi menunggu sampai kamu scroll mendekatinya. Mirip pelayan restoran yang menyajikan hidangan berikutnya waktu kamu hampir selesai makan, bukan semua sekaligus di awal sampai mejanya penuh. Hidangan pembuka tetap harus datang paling dulu.
🧪 Tebakan cepat: halaman portofolio-mu punya satu foto sampul (hero) di paling atas dan enam foto galeri di bawah. Kalau ketujuh gambar itu kamu kasih
loading="lazy", LCP-nya jadi lebih cepat, sama, atau lebih lambat? Tebak dulu, jawabannya ada di bagian "Jangan lazy gambar paling atas".
Satu atribut: loading="lazy"
Gak perlu JavaScript. Cukup satu atribut di tag <img> atau <iframe>, dan semua browser modern mengerti. Ini halaman portofolio di Lab:
<h1>My Portfolio</h1>
<!-- Hero: langsung terlihat, jadi TANPA lazy -->
<img id="hero" src="hero.jpg" width="1200" height="900"
fetchpriority="high" alt="Sampul portofolio">
<!-- Galeri: jauh di bawah, jadi lazy -->
<div class="gallery-section">
<img class="gallery" src="karya-1.jpg" loading="lazy"
width="800" height="600" alt="Karya pertama">
<img class="gallery" src="karya-2.jpg" loading="lazy"
width="800" height="600" alt="Karya kedua">
<img class="gallery" src="karya-3.jpg" loading="lazy"
width="800" height="600" alt="Karya ketiga">
</div>
<!-- Peta: iframe juga bisa lazy -->
<iframe src="map.html" loading="lazy" width="600" height="400"
title="Peta lokasi"></iframe>
loading="lazy": tunda unduhan sampai elemennya mendekati layar. Nilai bawaannyaloading="eager", artinya langsung diunduh. Jadi menuliseageratau gak menulis apa-apa hasilnya sama.- Tiap gambar galeri butuh
loading="lazy"sendiri. Atributnya gak menular dari gambar sebelumnya. <iframe>(peta, video YouTube) sering lebih berat dari gambar, karena dia memuat satu halaman utuh dengan script-nya. Tunda juga.widthdanheighttetap ditulis supaya tempatnya disiapkan dan gak ada layout shift saat gambarnya datang.
Di Lab, enam foto galeri masing-masing 80 KB, hero 180 KB, dan peta sekitar 600 KB. Tanpa lazy, browser mengunduh 1.260 KB begitu halaman dibuka. Dengan lazy di galeri dan peta, cuma 340 KB.
Kapan browser mulai mengunduh
Lazy bukan berarti "baru diunduh saat kelihatan". Chrome mulai mengunduh begitu jarak elemennya ke bawah layar tinggal 1.250 px (koneksi cepat seperti 4G) atau 2.500 px (koneksi lambat), supaya gambarnya sudah siap waktu kamu sampai.
Di Lab, layar HP-nya setinggi 640 px, jadi batasnya 640 + 1.250 = 1.890 px dari atas halaman:
| Elemen | Posisi atas | Mulai diunduh |
|---|---|---|
| karya-1 | 1.580 px | langsung saat dibuka (di bawah 1.890) |
| karya-2 | 1.842 px | langsung saat dibuka |
| karya-3 | 2.104 px | setelah scroll 214 px |
| karya-6 | 2.890 px | setelah scroll 1.000 px |
| peta | 3.300 px | setelah scroll 1.410 px |
Rumusnya: posisi atas dikurangi tinggi layar dikurangi 1.250. Karena itu 340 KB tadi berisi hero plus karya-1 dan karya-2.
Jangan lazy gambar paling atas
Gambar yang langsung terlihat saat halaman dibuka (sering disebut above the fold, di atas garis lipatan layar) biasanya jadi elemen LCP (Largest Contentful Paint), elemen terbesar di layar pertama. Kalau dia lazy, browser gak boleh mengunduhnya sebelum tahu posisinya, jadi harus menunggu CSS selesai dan layout dihitung. Waktu yang tadinya bisa dipakai mengunduh jadi terbuang.
Di Lab: hero tanpa lazy, LCP 1,5 detik. Hero diberi lazy, LCP jadi 1,9 detik. Jadi kalau ketujuh gambar diberi lazy, LCP-nya lebih lambat. Itu jawaban tebakannya.
Untuk gambar LCP, lakukan kebalikannya: tambahkan fetchpriority="high". Browser langsung memberinya prioritas High di antrean unduhan, bukan menunggu sampai tahu gambar itu terlihat. Di Lab, kalau galerinya gak lazy, enam foto galeri berebut koneksi dengan hero dan LCP jadi 1,8 detik. Dengan fetchpriority="high" di hero, LCP balik ke 1,5 detik.
Menunda hal lain: IntersectionObserver
loading="lazy" cuma untuk <img> dan <iframe>. Untuk menunda hal lain, misalnya komponen komentar atau data dari API, pakai IntersectionObserver: dia memanggil function-mu saat sebuah elemen mendekati layar.
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) { // elemennya sudah dekat layar
muatKomentar(entry.target); // baru ambil datanya sekarang
observer.unobserve(entry.target); // cukup sekali
}
});
}, { rootMargin: '200px' }); // mulai 200 px sebelum terlihat
observer.observe(document.querySelector('#komentar'));
rootMargin: '200px'memperluas batas layar 200 px, jadi kerjanya mulai sedikit sebelum elemennya terlihat.unobservemenghentikan pengamatan, supaya datanya gak diambil dua kali.
⚠️ Jebakan yang sering ketemu
- Lazy di hero. Gambar paling atas yang langsung terlihat jangan pernah diberi
loading="lazy". LCP-mu memburuk. - Cuma gambar pertama yang lazy. Tiap gambar galeri butuh atributnya sendiri. Kalau satu ketinggalan, yang itu tetap diunduh di awal.
- Lupa iframe. Embed peta atau YouTube sering bagian paling berat di halaman. Kasih
loading="lazy"juga. - Lazy tanpa
widthdanheight. Browser gak tahu tingginya, jadi saat gambar datang isi halaman bergeser.
TL;DR: loading="lazy" di <img> dan <iframe> menunda unduhan sampai elemennya mendekati layar (di Chrome: 1.250 px di koneksi cepat, 2.500 px di koneksi lambat). Di Lab, unduhan awal turun dari 1.260 KB ke 340 KB. Pasang di semua gambar galeri dan iframe yang jauh di bawah, lengkap dengan width dan height. Jangan pernah di gambar paling atas: hero yang lazy membuat LCP naik dari 1,5 ke 1,9 detik. Hero justru diberi fetchpriority="high". Untuk menunda hal lain, pakai IntersectionObserver.