mahirbrowser-apiobserverperformaMahir3 mnt baca

Intersection Observer: Deteksi Elemen Terlihat Tanpa Scroll Listener

Scroll listener yang jalan tiap piksel itu boros dan bikin web lag. Pelajari Intersection Observer: cara efisien untuk lazy-load gambar dan animasi saat terlihat.

Masalah: Scroll Listener Itu Boros

Cara lama mendeteksi "apakah elemen sudah terlihat di layar" adalah memasang listener pada event scroll, lalu menghitung posisi elemen dengan getBoundingClientRect() setiap kali pengguna menggeser halaman:

javascript
// JANGAN lakukan ini untuk banyak elemen
window.addEventListener("scroll", () => {
  const kotak = document.getElementById("promo");
  const posisi = kotak.getBoundingClientRect();
  if (posisi.top < window.innerHeight) {
    kotak.classList.add("muncul");
  }
});

Masalahnya: event scroll bisa menyala puluhan kali per detik, dan getBoundingClientRect() memaksa browser menghitung ulang layout (reflow) setiap kali dipanggil. Di HP kentang, halaman terasa patah-patah.

Solusi: Serahkan ke Browser dengan Intersection Observer

Intersection Observer (IO) membalik tanggung jawab: bukan JavaScript yang bertanya terus-menerus, melainkan browser yang memberi tahu saat sebuah elemen masuk atau keluar area pandang. Browser melakukannya secara asinkron dan teroptimasi di level engine, jauh lebih hemat daripada scroll listener manual.

Pola dasarnya hanya tiga langkah: buat observer dengan callback, tentukan elemen yang diamati, lalu bereaksi saat statusnya berubah:

javascript
const observer = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      entry.target.classList.add("muncul");
      observer.unobserve(entry.target); // cukup sekali
    }
  });
});

document.querySelectorAll(".kartu").forEach((kartu) => {
  observer.observe(kartu);
});

Callback menerima array entries, satu per elemen yang statusnya berubah. Properti terpenting adalah entry.isIntersecting (boolean: sedang terlihat atau tidak) dan entry.target (elemennya).

Contoh Realistis 1: Lazy-Load Gambar

Halaman katalog warung dengan 100 foto menu tidak perlu memuat semuanya sekaligus. Simpan URL asli di atribut data-src, dan isi src hanya saat gambar mendekati layar:

javascript
const pemuatGambar = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src; // baru unduh sekarang
      pemuatGambar.unobserve(img);
    }
  });
});

document.querySelectorAll("img[data-src]").forEach((img) => {
  pemuatGambar.observe(img);
});

Hasilnya: halaman terbuka instan karena hanya gambar di atas layar yang diunduh, kuota pengguna hemat, dan skor performa melonjak. Inilah teknik yang dipakai hampir semua situs berita dan e-commerce.

Contoh Realistis 2: Animasi Fade-In Kartu Menu

Animasi yang dipicu saat elemen terlihat terasa mewah tanpa JavaScript berat. Kuncinya: CSS menangani animasinya, IO hanya menambah class:

javascript
const animasi = new IntersectionObserver(
  (entries) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        entry.target.classList.add("terlihat");
        animasi.unobserve(entry.target);
      }
    });
  },
  { threshold: 0.2 } // picu saat 20% elemen terlihat
);

Dengan threshold: 0.2, animasi mulai sedikit sebelum elemen terlihat penuh, sehingga tidak ada elemen yang "melompat" tiba-tiba.

Mengatur threshold dan rootMargin

Dua opsi ini mengontrol kapan callback dipicu:

  • threshold: angka 0 sampai 1, porsi elemen yang harus terlihat. 0 berarti picu begitu satu piksel pun muncul; 1 berarti picu hanya saat seluruh elemen terlihat. Bisa juga array seperti [0, 0.5, 1] untuk dipicu di tiap tonggak.
  • rootMargin: memperbesar atau memperkecil area pandang, format seperti CSS margin. rootMargin: "200px" membuat callback dipicu 200px sebelum elemen masuk layar, sempurna untuk lazy-load agar gambar sudah siap saat pengguna sampai.
javascript
const awal = new IntersectionObserver(callback, {
  threshold: 0.5,
  rootMargin: "0px 0px 200px 0px", // 200px lebih awal dari bawah
});

Catatan teknis: secara default, root adalah viewport browser. Kamu bisa mengarahkannya ke container scroll tertentu (misalnya div chat) dengan root: document.getElementById("chat").

Jebakan Umum

  1. Lupa unobserve/disconnect: observer yang dibiarkan mengawasi elemen yang sudah ditangani membuang memori. Panggil unobserve(target) setelah selesai, atau disconnect() untuk berhenti total.
  2. Mengamati elemen yang display: none: elemen tak terlihat tidak pernah berpotongan, callback tidak akan jalan. Pastikan elemen punya ukuran.
  3. threshold 0 memicu terlalu dini: satu piksel terlihat langsung memicu. Untuk animasi, pakai 0.1-0.3 agar terlihat natural.
  4. Mengira IO menggantikan semua scroll logic: IO hanya tahu "terlihat atau tidak". Untuk parallax atau progress bar yang butuh posisi presisi, scroll listener (dengan throttle) masih diperlukan.
Live Playground
export default function App(): JSX.Element {
  return <h1>Hello world</h1>
}

Edit kode di kiri, preview kanan ter-update otomatis.

Tantangan

Lazy-Load Daftar Gambar

Di dunia nyata, lazy-load adalah alasan halaman katalog dan berita terbuka cepat di HP kentang: gambar hanya diunduh saat mendekati layar. Kuasai Intersection Observer dan kamu bisa menghemat kuota pengguna secara signifikan. Tugasmu: buat 5 <img> dengan data-src berisi URL gambar (pakai https://picsum.photos/seed/N/300/200), lalu dengan Intersection Observer isi atribut src dari data-src saat gambar terlihat, dan berhenti mengamatinya.

<!doctype html>
<html>
<head>
<meta charset="utf-8" />

</head>
<body>
<h1>Halo JS</h1>
<p>Buka console preview untuk melihat output.</p>
<script src="index.js"></script>
</body>
</html>

Kuis Bab

Uji pemahamanmu: Event & Browser API

Jawab 5 soal berikut, lalu tekan "Periksa Jawaban".

1.Apa perbedaan konsep debounce dan throttle?

2.Apa perbedaan utama localStorage, sessionStorage, dan cookie?

3.Apa perbedaan setTimeout dan setInterval?

4.Untuk kasus apa Intersection Observer paling tepat?

5.Apa gunanya custom event dengan properti detail?