Improve Scroll Performance with Passive Event Listeners

Alex Reardon
InstructorAlex Reardon

Share this video with your friends

Send Tweet
Published 6 months ago
Updated 4 months ago

This lesson will explore what problem passive event listeners are trying to solve, and how you can mark your event listeners as passive to improve scrolling performance. It also covers how some browsers automatically set touchstart, touchmove, wheel and mousewheel events added to the window, document or body as passive by default.

Instructor: [0:00] Here, we have a setup where there's a fair amount of event listeners on an event path. Let's pretend these event listeners are listening for the wheel event. A web page can opt out of native scrolling by canceling one of a few different events, including the wheel event.

[0:21] If a user is trying to scroll the page, the browser has to wait for our wheel event listeners to be processed before the browser can know if native scrolling is allowed to happen. It has to wait to see if the event was canceled. This delay can be significant and can make a web page feel unresponsive.

[0:44] This delay can be even worse if the browser is already executing some other task in the call stack, which needs to be finished before the wheel event can even start to be processed. The solution to this problem is passive event listeners.

[1:03] A passive event listener is like any other event listener except the event listener says up front that it will not cancel an event. If all of our event listeners on our event path are passive, then the browser does not need to wait for the event path to be processed to know if an event will be canceled. It already knows the event cannot be canceled.

[1:31] In our case of scrolling a web page, if all of the event listeners on our wheel event path are passive, then the browser can allow the native scroll to start straightaway, without needing to wait for any JavaScript. The browser can then process the wheel event in the call stack at a later point.

[1:50] If any of our event listeners on the event path are not passive, then the browser has to wait for that event listener to be executed before it can know if the event will be canceled. To register an event listener, add it with addEventListener() as passive. You can set passive to true as a part of the addEventListener() options object.

[2:16] You can use the passive property in combination with any of the other addEventListener() option object properties. Here, I am adding a wheel event listener to the window. In my onwheel function, I'm going to log our wheel to the console. For this lesson, I have made the body element of my example scrollable. When I scroll the body with my mouse wheel, I see that wheel is logged out to the console.

[2:48] Passive event listeners are the same as any other event listener, except they are not permitted to cancel events. Calling event.preventDefault() inside of a passive event listener will not cancel an event. I'm going to try and cancel the event with event.preventDefault() and then I'm going to log out whether the event was canceled by looking at the event.defaultPrevented property.

[3:17] Let's see what happens when I try and scroll the web page with my mouse wheel. We'll see that a few things happened. Firstly, the browser yelled at us. It says, it was unable to preventDefault inside of a passive event listener.

[3:32] We broke the contract of a passive event listener by trying to cancel the event. We'll say that our attempt to cancel the event was unsuccessful and as a result of the event not being canceled, our web page was scrolled by the wheel event.

[3:50] If we change passive to false, and then we come back to the browser, and I try and scroll the web page with my mouse wheel again, we'll see that our wheel event was canceled, and the scrolling of the page did not occur. The default behavior of scrolling the page was prevented by our canceling of the event, in our non-passive wheel event listener.

[4:13] Generally speaking, passive event mechanics are only used by browsers for events, that if canceled, would prevent scrolling. These are touchstart, touchmove, wheel, and a non-standard mousewheel events. There does not appear to be any benefits to using passive event listeners for non-scrolling related events, such as click.

[4:39] Some browsers have decided to make the touchstart, touchmove, wheel, and mousewheel event listeners passive by default when attached to the window, document, or the body. Now, I'm going to take away my explicit setting of passive to true on this wheel event listener.

[4:56] If we come over to my page, and I try and scroll the page with my mouse wheel, we'll see that I got a similar warning saying, I was unable to call preventDefault() inside a passive event listener due to the target, in this case, the window, being treated as passive.

[5:12] You can opt out of these default passive values by explicitly setting your own passive property in your addEventListener() options object. Here, I am explicitly setting my wheel event listener to not be passive. When I come over here and I try and scroll the page with my mouse wheel, I see that I'm able to cancel the event, and the default behavior of scrolling the page was prevented.

[5:36] Whether a particular event for a particular target is passive by default does seem to be different between browsers. Because of this, it is always safest to explicitly define an event listener as passive if you are listening to the touchstart, touchmove, wheel or mousewheel events. You do not need to cancel the event in your event listener.

[5:57] The ontouchstart, ontouchmove, onwheel and onmousewheel object property event handlers use the default passive value for the event type on that target. There is no way to parse in a passive value to an object property event handler, so you'll always get the default passive value.

[6:16] Here, I'm adding an onwheel object property event handler to the window. I'm going to try and cancel the event, and then log out whether the event was canceled. Here, I'm seeing the same warning I saw before, where I'm unable to call preventDefault() inside a passive event listener due to the target being treated as passive.

[6:36] Wheel events on the window, document and body are treated as passive by default in Chrome, and so the onwheel property is also seeing the same behavior. Our onwheel function can't opt out of this behavior. There's no way for me to make this onwheel object property event handler not passive.