Passive vs Non-Passive Event Listeners in JavaScript
Passive and non-passive event listeners determine how a browser handles user input events—such as scrolling, touch, and wheel interactions—relative to page rendering. While non-passive event listeners can block the main thread and delay scrolling while the browser waits to check if the action will be canceled, passive event listeners inform the browser upfront that the event will not be canceled, allowing the browser to perform smooth scrolling immediately on a separate background thread.
The Mechanism Behind Non-Passive Event Listeners
By default, standard event listeners are non-passive
({ passive: false }). When a user scrolls via a touch
gesture (touchstart, touchmove) or a mouse
wheel (wheel), the browser generates an event. Because
JavaScript allows developers to cancel the default scrolling behavior
using event.preventDefault(), the browser must pause
scrolling and wait for the JavaScript event listener to finish executing
on the main thread.
If the main thread is occupied with heavy computations, layout recalculations, or complex DOM updates, this waiting period causes a noticeable delay between the user’s gesture and the actual movement of the screen. This delay is known as “scroll jank.”
How Passive Event Listeners Improve Performance
Passive event listeners ({ passive: true }) solve scroll
jank by indicating to the browser that the handler will never invoke
event.preventDefault().
Because the browser is guaranteed that the scroll action cannot be canceled, it does not need to wait for the JavaScript thread to finish executing the callback function. Instead, the browser’s compositor thread handles the scroll animation immediately and asynchronously. The JavaScript event listener still executes, but it runs concurrently without blocking the user’s visual feedback.
If event.preventDefault() is accidentally called inside
a passive listener, the browser simply ignores the call and generates a
warning in the developer console.
Implementation Comparison
You can specify whether an event listener is passive by passing an
options object as the third argument to
addEventListener:
// Non-passive listener: May cause scroll lag
window.addEventListener('touchmove', handleScroll, { passive: false });
// Passive listener: Enables smooth, non-blocking scrolling
window.addEventListener('touchmove', handleScroll, { passive: true });Key Differences
- Main Thread Blocking: Non-passive listeners force the browser to wait for the main thread before scrolling; passive listeners allow the compositor thread to scroll instantly.
- Use of
preventDefault(): Non-passive listeners allow canceling the default scroll behavior; passive listeners disallow it to ensure uninterrupted rendering. - User Experience: Passive listeners eliminate scroll stutter and latency, creating a significantly smoother experience on mobile devices and trackpads.
- Modern Defaults: To improve performance, modern web
browsers treat touch and wheel events attached to
window,document, andbodyas passive by default unless explicitly specified otherwise.