Quick Tip: How to Throttle Scroll Events
This article was peer reviewed by Vildan Softic and Julian Motz. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!
One of the perils of listening to scroll events is performance degradation. The browser will execute the callback every single time the user scrolls, which can be many events per second. If the callback performs a bunch of repaints, this means bad news for the end user. Repaints are expensive, especially when you are redrawing large parts of the view, such as is the case when there is a scroll event.
The example below illustrates the issue:
See the Pen Unthrottled Scroll Events by SitePoint (@SitePoint) on CodePen.
Apart from performance degradation and being prone to causing seizures. This example illustrates what happens when the computer does exactly what you tell it to do. There is no smooth transition between background colors and the screen just flickers. Any hapless programmer may feel as if there is no hope left in this world. Isnʼt there a better way?
Regulating Events
One solution is to defer events and manage a bunch of them at once. There are two commonly used functions that can help us with this: throttle and debounce.
Throttle guarantees a constant flow of events at a given time interval, whereas debounce groups a flurry of events into one single event. One way to think about it is throttle is time-based and debounce is event driven. Throttling guarantees execution while debounce does not once grouping has occurred. If you want to know the specifics, check out this in-depth discussion of debounce versus throttling.
Debounce
Debounce solves a different problem, such as key presses with Ajax. When you type in a form, why would you send a request per keystroke? A more elegant solution is to group a burst of keystrokes into one event that will trigger the Ajax request. This fits within the natural flow of typing and saves server resources. With key presses, the interval between events isn’t important, since users donʼt type at a constant rate.
Throttle
Since there are no guarantees with debounce, the alternative is to throttle the scroll events. Scrolling occurs on a given time span, so it is fitting to throttle. Once the user begins scrolling, we want to guarantee execution on a timely basis.
This technique helps with checking if we are at a specific part of the page. Given the size of the page, it takes many seconds to scroll through content. This enables throttling to fire the event only once at any given interval. Event throttling will make the scrolling experience smoother and guarantee execution.
Below is a poor manʼs event throttler in vanilla JavaScript:
function throttle(fn, wait) {
var time = Date.now();
return function() {
if ((time + wait - Date.now()) < 0) {
fn();
time = Date.now();
}
}
}
This implementation sets a time
variable, which tracks when the function is first called. Each time the returned function is called it checks if the wait
interval has passed and if so it fires the callback and resets time
.
See the Pen Vanilla JS Throttle Implementation by SitePoint (@SitePoint) on CodePen.
Using a Library
Event throttling has many perils and it is not recommended to cook one up in-house. Rather than writing your own implementation, I recommend using a 3rd party implementation.
lodash
lodash is the de-facto standard for event throttling in JavaScript. This library is open source so feel free to explore the code. What is nice is the library is modular so it is possible to grab just what you need from it.
Scroll event throttling becomes simple with lodash’s throttle function:
window.addEventListener('scroll', _.throttle(callback, 1000));
This limits the flurry of incoming scroll events to one every 1,000 milliseconds (one second).
The API offers a leading
and trailing
option as such:
_.throttle(callback, 1, { trailing: true, leading: true });
These options determine whether the callback gets executed on the leading and / or trailing edge.
One gotcha here is if you specify leading and trailing set to false
, the callback does not fire. Setting the leading to true
will begin callback execution immediately and then throttle. When you set both leading and trailing to true
, this guarantees execution per interval.
Check out the demo on CodePen: Throttling Scrolling Events
What I find interesting from looking at the source code is that throttle()
is just a wrapper around debounce()
. Throttling just passes a different set of parameters to change the desired behavior. Throttle sets a maxWait
that guarantees the execution once it waits that long. The rest of the implementation remains the same.
I hope you find lodash beneficial in your next event throttling adventure!
Conclusion
The key to throttle vs debounce is to look at the nature of the problem you are to solve. The two techniques are suitable for different use cases. What I recommend is to look at the problem from the userʼs perspective to find the answer.
The beauty of using lodash is it abstracts a ton of complexity in a simple API. The good news is you can use _.throttle()
in your projects without having to add the whole library. There is lodash-cli, a tool that lets you create a custom build with only the functions that you need. Event throttling is only a tiny fraction of the entire library.