Skip to content

How to Fix Scroll Event Performance in JavaScript

DodaTech Updated 2026-06-24 2 min read

In this tutorial, you'll learn about How to Fix Scroll Event Performance in JavaScript. We cover key concepts, practical examples, and best practices.

The Problem

Scroll event listeners cause jank and performance issues because the scroll event fires at a high rate (60+ times per second), triggering expensive layout calculations, repaints, or JavaScript execution on every frame.

Quick Fix

Step 1: Add a passive listener

A passive listener tells the browser it will not call preventDefault(), enabling scroll optimization:

window.addEventListener('scroll', () => {
    updateLayout();
});

This blocks scrolling while updateLayout runs. Make it passive:

window.addEventListener('scroll', () => {
    updateLayout();
}, { passive: true });

Step 2: Throttle the handler

Limit how often the handler runs using requestAnimationFrame:

let ticking = false;
window.addEventListener('scroll', () => {
    if (!ticking) {
        window.requestAnimationFrame(() => {
            updateLayout();
            ticking = false;
        });
        ticking = true;
    }
}, { passive: true });

Step 3: Use IntersectionObserver instead of scroll

For detecting element visibility, IntersectionObserver is more performant:

const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            entry.target.classList.add('visible');
            observer.unobserve(entry.target);
        }
    });
}, { threshold: 0.1 });
document.querySelectorAll('.lazy-section').forEach(el => observer.observe(el));

Step 4: Debounce for non-visual updates

For analytics or saving scroll position, use debounce:

function debounce(fn, delay = 150) {
    let timer;
    return (...args) => {
        clearTimeout(timer);
        timer = setTimeout(() => fn(...args), delay);
    };
}
window.addEventListener('scroll', debounce(() => {
    saveScrollPosition(window.scrollY);
}, 200), { passive: true });

Prevention

  • Always use { passive: true } for scroll and touch event listeners.
  • Use requestAnimationFrame to throttle scroll handlers.
  • Replace scroll-based visibility checks with IntersectionObserver.
  • Use CSS overflow-anchor: auto for scroll anchoring instead of JS.
  • Profile scroll performance in DevTools Performance panel.

Common Mistakes with scroll event

  1. Mixing let bindings with <- bindings in do notation, producing type errors
  2. Overlapping type class instances that cause GHC to reject the program with ambiguous dispatch errors
  3. Non-exhaustive pattern matches that compile with warnings then crash at runtime

These mistakes appear frequently in real-world JS code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.

Practice Exercise

Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.

This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.

FAQ

### Why does passive improve scroll performance?

A passive listener signals that the handler will not call preventDefault(). The browser can then optimize scrolling by starting it immediately without waiting for the JavaScript handler to run. Without passive, the browser waits to see if the handler cancels scrolling, introducing latency on every frame.

What is the difference between throttling and debouncing?

Throttling ensures a function runs at most once per interval (e.g., every 16ms for scroll). Debouncing delays execution until after activity stops (e.g., 200ms after the last scroll). Use throttling for continuous events like scroll. Use debouncing for events that should run only after the user stops, like search-as-you-type.

Does IntersectionObserver work in all browsers?

IntersectionObserver is supported in all modern browsers. For older browsers (IE11), use a polyfill or fall back to scroll-based detection. The API is widely supported and recommended as the standard approach for lazy loading, animation triggers, and visibility tracking.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro