Get it?

The observer API has landed for some time now and is fully supported by all modern browsers. One of them, is the IntersectionObserver which helps you get callbacks fired when certain DOM elements “intersect” with one another. I questioned myself “Could I replace my scroll listeners with that? How cheaper and performance friendlier would it be”? Well this article attempts to answer just that.

Setting the scene

To properly compare these 2 approaches, I created an incredibly simple dummy SPA app: a single page with 4 sections of text in it. As you scroll from one section to the other, the the hash of the webpage is updated based on which section you are viewing right now. Pretty simple eh? If you still don’t full get it, I created a small GIF to further illustrate my point (a link to that code can be found at the very bottom of the article).

Our benchmark — a dummy SPA with 4 scrollable sections

I created 2 versions of this page; one with a scroll listener and one with intersection observers. For the intersection observers, the code is pretty straightforward and you don’t have to make any performance hacks & tricks. For the scroll listener, I created 3 different versions of them.

1. No offset caching, no Throttling

This version simply adds the needed scroll listener without caching any values that might stay the same (namely, the offset distance of each section) or adding any throttling to the scroll listener.

2. Offset caching, No Throttling

This version caches the offset distances of the sections (which stay the same as long as the viewport width stays the same), but doesn’t add any throttling to the scroll listener.

3. Offset caching & Throttling

This version is exactly like (2) but with a throttling of 300ms on the listener’s callback.

Benchmarks

The comparisons have been made using the performance tab in Chrome’s Devtools. To properly simulate a computer with average power, I added a 4x slowdown on the CPU. Then I took 4 performance recordings (3 with the different variations of the scroll listeners and 1 with the intersection observers implementation) while scrolling from the top of the page to the very bottom without stopping at all. The results were the following:

Scroll Listener — No caching & No throttling

Scroll Listener — Caching & No throttling

Scroll Listener — Caching & throttling

Intersection Observer

A quick explanation:

The yellow essentially means computational stress. The higher, the more work the main thread has to do. The green above the yellow are the FPS. The higher & steadier it is, the better experience the user will have. The small red dots above the green can mean a lot of things (generally lagginess for the user). In this case they mean that the callback time extended beyond one Event loop. The less they appear the better :)

If we accumulate all the “yellow” time and divide it by the total time, we get the percentage of time spent doing scripting work on the main thread:

Percentage of total time doing scripting work

Scroll Listener — No Caching & No Throttling: 48.9%

Scroll Listener — Caching & No Throttling: 43.5%

Scroll Listener — Caching & Throttling: 28.9%

Intersection Observer: 23.3%

A couple of interesting things to see is that the IntersectionObserver is only comparable to an optimised ScrollListener with throttling present. That’s interesting, since we didn’t have to add any performance optimisations to the observer itself. If we slow down the CPU even further to a 6x slowdown (simulating older mobile phones) and do the same test again, then the results are even more clear:

Scroll Listener — Caching & Throttling

Intersection Observer

Percentage of total time doing scripting work

Scroll Listener — Caching & Throttling: 63.0%

Intersection Observer: 37.6%

That means that when using an intersection observer, the main thread is 43% more free to respond to user input (on extremely slow machines)

Conclusion

What we attempted to do here, is compare the performance benefits of two different solutions that solve the same problem. Of course there are times when a scroll listener might be the best way to go (due to external limitations), but for the core tasks (such as changing the hash, altering some styles based on scrolling, making something stick after a certain point, etc.) the new observer API does perform better and should be preferred if performance is crucial for your app.

Lastly, the code used to reproduce the results above can be found here:

https://codesandbox.io/s/x7n0kmxjvo?fontsize=14

Thanks for reading!

P.S. 👋 Hi, I’m Aggelos! If you liked this, consider following me on twitter and sharing the story with your developer friends 😀

You may also be interested in some of my stories from the previous weeks: