Scroll terasa berat karena handler scroll berjalan puluhan kali per detik di main thread browser. Perbaikan tercepat: jadikan listener passive, batasi kerja dengan throttle berbasis requestAnimationFrame, lalu pindahkan query DOM keluar dari handler. Pola ini menyelesaikan sebagian besar kasus sticky header, navbar hide/show, parallax, dan infinite scroll yang patah-patah.
Kenapa Scroll Event Terasa Berat di Browser
Browser memanggil listener scroll setiap ada perubahan posisi scroll, bukan sekali per gerakan. Handler yang membaca offset, mengubah style, atau mencari elemen setiap kali dipanggil akan menumpuk dan memblokir rendering.
- Frekuensi scroll tinggi membuat fungsi berat dieksekusi berulang tanpa jeda.
- Baca lalu tulis DOM bergantian memicu layout thrashing karena browser menghitung ulang layout berkali-kali.
- Listener non-passive membuat browser menunggu handler selesai sebelum melanjutkan scroll.
Diagnosis Cepat: Kondisi | Penyebab | Solusi
Semua kode di artikel ini berjalan di browser, bukan Node.js. Rekam aktivitas scroll lewat tab Performance di DevTools, lalu cocokkan gejalanya.
| Kondisi | Penyebab | Solusi |
|---|---|---|
| Scroll tersendat di semua halaman | Handler melakukan banyak kerja tiap event | Throttle dengan requestAnimationFrame |
| Parah hanya saat elemen berubah | querySelector dan tulis style di dalam handler | Cache elemen dan pisahkan baca/tulis DOM |
| Scroll tertahan lalu melompat | Listener non-passive plus animasi berat | Gunakan passive true dan IntersectionObserver |
Cara Mengatasi Scroll Event Berat Javascript
Ikuti urutan ini karena setiap langkah menghilangkan satu sumber macet yang berbeda.
1. Tandai listener sebagai passive: true bila handler tidak memanggil preventDefault.
2. Batasi handler dengan throttle atau requestAnimationFrame agar kerja berat maksimal sekali per frame.
3. Simpan hasil querySelector di variabel luar, jangan mencari elemen setiap event.
4. Baca semua nilai layout dulu, baru tulis style sekaligus agar browser tidak reflow berulang.
const header = document.querySelector('.site-header');
let ticking = false;
function updateHeader() {
const y = window.scrollY;
header.classList.toggle('is-solid', y > 80);
ticking = false;
}
window.addEventListener('scroll', () => {
if (!ticking) {
ticking = true;
requestAnimationFrame(updateHeader);
}
}, { passive: true });
Kode di atas mengunci pembaruan ke satu frame. Variabel ticking mencegah antrean callback, sedangkan header yang di-cache menghilangkan pencarian DOM berulang.
Cara Mencegah Scroll Kembali Berat
- Pakai IntersectionObserver untuk sticky header, lazy load gambar, dan animasi reveal.
- Lepas listener dengan removeEventListener dan batalkan requestAnimationFrame memakai cancelAnimationFrame saat komponen dihancurkan.
- Uji ulang dengan scroll cepat dari atas sampai bawah halaman setelah tiap perbaikan.
FAQ
Apakah debounce cocok untuk scroll event berat?
Tidak untuk efek visual saat scroll karena debounce menunggu gerakan berhenti, jadi tampilan terlambat diperbarui.
Kapan memakai IntersectionObserver dibanding scroll listener?
Pakai IntersectionObserver saat hanya perlu tahu elemen masuk atau keluar viewport, seperti lazy load dan reveal.
Apakah passive true selalu aman dipakai?
Tidak, hindari bila handler perlu preventDefault karena browser akan mengabaikan pemanggilan tersebut.