Making wheel scrolling fast by default

Sahel Sharify
Sahel Sharify

To improve wheel scrolling/zooming performance, developers are encouraged to register wheel and mousewheel event listeners as passive by passing the {passive: true} option to addEventListener(). Registering the event listeners as passive tells the browser that the wheel listeners will not call preventDefault() and the browser can safely perform scrolling and zooming without blocking on the listeners.

The problem is that most often the wheel event listeners are conceptually passive (do not call preventDefault()) but are not explicitly specified as such, requiring the browser to wait for the JS event handling to finish before it starts scrolling/zooming even though waiting is not necessary. In Chrome 56, we fixed this issue for touchstart and touchmove, and that change was later adopted by both Safari and Firefox. As you can see from the demonstration video we made at that time, leaving the behavior as it was produced a noticeable delay in scroll response. Now in Chrome 73, we've applied the same intervention to wheel and mousewheel events.

The Intervention

Our goal with this change is to reduce the time it takes to update the display after the user starts scrolling by wheel or touchpad without developers needing to change code. Our metrics show that 75% of the wheel and mousewheel event listeners that are registered on root targets (window, document, or body) do not specify any values for the passive option and more than 98% of such listeners do not call preventDefault(). In Chrome 73, we are changing the wheel and mousewheel listeners registered on root targets (window, document, or body) to be passive by default. It means that an event listener like:

window.addEventListener("wheel", func);

becomes equivalent to:

window.addEventListener("wheel", func, {passive: true});

And calling preventDefault() inside the listener will be ignored with the following DevTools warning:

[Intervention] Unable to preventDefault inside passive event listener due
to target being treated as passive. See https://www.chromestatus.com/features/6662647093133312

Breakage and Guidance

In the vast majority of cases, no breakage will be observed. Only in rare cases (less than 0.3% of pages according to our metrics), unintended scrolling/zooming might happen due to the preventDefault() call getting ignored inside the listeners that are treated as passive by default. Your application can determine whether it may be hitting this in the wild by checking if calling preventDefault() had any effect via the defaultPrevented property. The fix for the affected cases is relatively easy: pass {passive: false} to addEventListener() to override the default behavior and preserve the event listener as blocking.