Your heart may be in the right place. There's no point in thrashing the CPU for events like mousemove and scroll that are theoretically going to fire hundreds of times per second, right? But have you checked whether or not your throttling is actually necessary?
Consider the following throttling function that uses requestAnimationFrame() to ensure that the supplied function runs no more often than the browser's repaint frequency.
function throttleRaf(fn) {
let busy = false;
return function (…args) {
if (! busy) {
requestAnimationFrame(() => {
fn.apply(this, args);
busy = false;
});
busy = true;
}
};
}
For most setups this will limit fn to being called 60 times per second as 60 is the default target frame rate. And if the system is slow and can only manage 30 frames per second, the throttling will follow suit. Convenient!
But is it necessary? I had such a function in play on this very site, but as I kept digging around MDN I came across a curious note:
Note that you may see code that throttles the
scrollevent handler usingrequestAnimationFrame(). This is useless because animation frame callbacks are fired at the same rate asscrollevent handlers. Instead, you must measure the timeout yourself, such as by usingsetTimeout().
Oh? This was the first time I'd heard such a thing. Let's do a little test to see if that's true. When our throttled function is called while busy, let's drop a line into the console about it.
function throttleRaf(fn) {
let busy = false;
return function (…args) {
if (! busy) {
requestAnimationFrame(() => {
fn.apply(this, args);
busy = false;
});
busy = true;
} else {
console.log('Call prevented!');
}
};
}
Scroll around all you like, but that line will never be logged. The system will never be busy when it's time for your on-scroll function to run. In my case I was using mousemove but the results were the same.
It turns out that a number of would-be problematic events are synced to animation frames out of the box, limiting their potential for "spam" without you having to do anything extra in your code. Since Google's search results are terrible to the point of near uselessness these days, I asked ChatGPT which events were synced like this. It said:
pointermovemousemovemouseenter/mouseleave/mouseover/mouseoutpointerover/pointerout/pointerenter/pointerleavescrollwheel(on most browsers, though some may deliver in batches)
Laziness prevents me from verifying this myself, but when asked about its source it said it used the standards put forth by W3C, WHATWG, etc. Feel free to use the throttling function above, the one with the console.log() line, to confirm it yourself. And sometimes you really ought to confirm such things because ChatGPT is sometimes incredibly stupid.
In this case it seems to be correct, though. While you might get a case of the premature optimizations when thinking about using the scroll event and others like it, the simple truth is that certain events that sound problematic actually aren't because they're already synced to repaint frames.
That doesn't mean you should never throttle your event handlers. Manipulating the DOM is an expensive operation, for instance, so if you do such a thing on scroll you'll likely bog down the system and make your site feel bad. In that case you do want to throttle your handler, but you'll want it to be at notably slower rate than what requestAnimationFrame() will provide.
Better yet, you can (or should) debounce your expensive event handlers so that they happen only after a series of rapid-fire events have stopped.
Debouncing is very similar to throttling. The key difference is that throttling enforces limits on continuous operations, while debouncing waits for invocations to stop for a specific time to consolidate many noisy invocations into one single invocation.
The user scrolls a bunch, finally finds what they're looking for and stops, and then you mess with DOM, for instance. Rather than provide my own mediocre code for such a thing, I'll link you to two good ones:
No point in making your own rough, wobbly wheel when someone's offering up a perfectly smooth and well-tested wheel to you for free!