Skip to main content Accessibility Feedback

Detecting scroll distances with vanilla JS

This morning, Sami Keijonen, one of my students, asked me if there was a way to detect how far a user has scrolled (and in what direction) using vanilla JS.

There’s not a native method for this, but lets cobble a small helper function together.

Getting setup

First, let’s create a helper function called scrollDistance(), that accepts a callback function to run when the scroll is complete.

If there’s no callback, or if it’s not a function, we’ll just bail.

var scrollDistance = function (callback) {

	// Make sure a valid callback was provided
	if (!callback || typeof callback !== 'function') return;

};

Next, let’s setup some variables to hold details about whether or not the user is actively scrolling, their starting and ending positions, and the total distance they scrolled.

var scrollDistance = function (callback) {

	// Make sure a valid callback was provided
	if (!callback || typeof callback !== 'function') return;

	// Variables
	var isScrolling, start, end, distance;

};

Now we’re ready to actually detect scrolls.

Detecting scrolling distances

To make this work, we need to detect scroll events using addEventListener().

var scrollDistance = function (callback) {

	// Make sure a valid callback was provided
	if (!callback || typeof callback !== 'function') return;

	// Variables
	var isScrolling, start, end, distance;

	// Listen for scroll events
	window.addEventListener('scroll', function (event) {\
		// Scrolling has happened...
	}, false);

};

When scrolling starts, if the start variable has no value, we’ll cache the window.pageYOffset—the user’s current position on the page—to it.

Then, we’ll assign a setTimeout() function to the isScrolling variable.

By default, we’ll have it run every 66 milliseconds (roughly the refresh rate of computer screens). This will defer our callback function from running until the scroll is complete. Let’s add a refresh argument to our function so that the delay can be customized.

var scrollDistance = function (callback, refresh) {

	// Make sure a valid callback was provided
	if (!callback || typeof callback !== 'function') return;

	// Variables
	var isScrolling, start, end, distance;

	// Listen for scroll events
	window.addEventListener('scroll', function (event) {

		// Set starting position
		if (!start) {
			start = window.pageYOffset;
		}

		// Set a timeout to run after scrolling ends
		isScrolling = setTimeout(function() {
			// Calculate distances and run our callback
		}, refresh || 66);

	}, false);

};

On every scroll event, we’ll run clearTimeout() to prevent the callback from running while there’s scrolling actively happening.

var scrollDistance = function (callback, refresh) {

	// Make sure a valid callback was provided
	if (!callback || typeof callback !== 'function') return;

	// Variables
	var isScrolling, start, end, distance;

	// Listen for scroll events
	window.addEventListener('scroll', function (event) {

		// Set starting position
		if (!start) {
			start = window.pageYOffset;
		}

		// Clear our timeout throughout the scroll
		window.clearTimeout(isScrolling);

		// Set a timeout to run after scrolling ends
		isScrolling = setTimeout(function() {
			// Calculate distances and run our callback
		}, refresh || 66);

	}, false);

};

Calculating the distance scrolled

Once our setTimeout() function actually runs, we can calculate the distance scrolled.

We’ll set end to the new current window.pageYOffset, and distance to the difference between start and end. If distance is negative, the user scrolled up. If it’s positive, they scrolled down.

We’ll pass all three variables into the callback() function.

var scrollDistance = function (callback, refresh) {

	// Make sure a valid callback was provided
	if (!callback || typeof callback !== 'function') return;

	// Variables
	var isScrolling, start, end, distance;

	// Listen for scroll events
	window.addEventListener('scroll', function (event) {

		// Set starting position
		if (!start) {
			start = window.pageYOffset;
		}

		// Clear our timeout throughout the scroll
		window.clearTimeout(isScrolling);

		// Set a timeout to run after scrolling ends
		isScrolling = setTimeout(function() {

			// Calculate distance
			end = window.pageYOffset;
			distance = end - start;

			// Run the callback
			callback(distance, start, end);

		}, refresh || 66);

	}, false);

};

Finally, we’ll reset all of our variables for the next scroll.

var scrollDistance = function (callback, refresh) {

	// Make sure a valid callback was provided
	if (!callback || typeof callback !== 'function') return;

	// Variables
	var isScrolling, start, end, distance;

	// Listen for scroll events
	window.addEventListener('scroll', function (event) {

		// Set starting position
		if (!start) {
			start = window.pageYOffset;
		}

		// Clear our timeout throughout the scroll
		window.clearTimeout(isScrolling);

		// Set a timeout to run after scrolling ends
		isScrolling = setTimeout(function() {

			// Calculate distance
			end = window.pageYOffset;
			distance = end - start;

			// Run the callback
			callback(distance, start, end);

			// Reset calculations
			start = null;
			end = null;
			distance = null;

		}, refresh || 66);

	}, false);

};

Demos and such

Here’s a demo on CodePen. You can also snag the completed helper method on the Vanilla JS Toolkit.

This works in all modern browsers, and IE9 and up.