H

July 30, 2015

I've always been a stickler when it comes to throttling events that occur frequently in JavaScript—especially scroll and resize listeners. The most performant way to accomplish heavy front-end related tasks in the callbacks of those listeners is to let window.requestAnimationFrame do the work when the browser has a chance. Rather than do it all in a setTimeout callback which could lead to a janky experience.

Naturally we would write our scroll listener like so (jQuery for brevity):

(function($) {
	$(window)
		.on('scroll', function() {
			// do something that triggers a repaint,
			// but don't throttle it because we like
			// janky web experiences.
		})
		.trigger('scroll');
})(jQuery);

Great, so now we have a highly unoptimized scroll listener that's going to trigger thousands of times.

Let's instead put this into a requestAnimationFrame call:

(function($) {

	var scrollAction = function() {
		// do something that triggers a repaint
	};

	$(window)
		.on('scroll', function() {
			window.requestAnimationFrame(scrollAction);
		})
		.trigger('scroll');
})(jQuery);

Not bad, but now we're going to queue up thousands of calls to requestAnimationFrame, which doesn't really help our performance issue.

Let's only call requestAnimationFrame after the last call to requestAnimationFrame has "ended":

(function($) {

	var working = false;

	var scrollAction = function() {
		// do something that triggers a repaint
		working = false;
	};

	$(window)
		.on('scroll', function() {
			if (! working) {
				window.requestAnimationFrame(scrollAction);
				working = true;
			}
		})
		.trigger('scroll');
})(jQuery);

Now we've added the working semaphore which is toggled whenever the animation frame has been completed. This ensures that if your browser has a hiccup while scrolling, there won't be additional calls to scrollAction which may lead to weird results.

This feature has been available in all major browsers for quite some time now. There's even a polyfill for those needing support on IE 9 and below. There are also plenty of other features you can use to optimize your performance in scroll listeners, such as the newer CSS property will-change.

Next up: Writing a PostCSS Plugin

Proceed