Adding Headroom with JavaScript

Internet Browser
Image by janjf93 from Pixabay

Fixed navigation bars have an annoying drawback — they take up precious screen real estate. We can add headroom to any web page by automatically retracting and expanding the navigation bar on Of course, retracting and expanding navigation bars can be even more annoying. In the Improvements section we try to ease this pain.

We will create a super simple implementation with Vanilla JavaScript and discuss some ways to improve its behavior.

First let’s add some hacker style boilerplate to our app.js file like so;

/**
 * Boilerplate Functions
 */
function posf (f, a) { for (var i=0; i < a.length; i++) { if (f(a[i])) return i; } return -1; }
function apos (x, a) { return (typeof x == 'function') ? posf(x,a) : Array.prototype.indexOf.call(a,x) }
function arem (a, x) { var i = apos(x, a); if (i >= 0) { a.splice(i, 1); } return a; }
function afind (x, a) { var i = apos(x, a); return (i >= 0) ? a[i] : null; }
function addClass (el, cl) { if (el) { var a = el.className.split(' '); if (!afind(cl, a)) { a.unshift(cl); el.className = a.join(' ')}} }
function remClass (el, cl) { if (el) { var a = el.className.split(' '); arem(a, cl); el.className = a.join(' ') } }

We will add our variables. The variable previousPosition will store the initial page position, and then we will bind to our navigation bar using the navbar selector and obtain its height.

/**
 * Setup Variables
 */
var previousPosition = window.pageYOffset;
var navbar = document.getElementById("navbar");
var navbarHeight = navbar.offsetHeight;

We will then add an onscroll event with a simple check that hides the navigation bar on scroll down and reveals it on scroll up.

/**
 * Headroom Action
 */
window.onscroll = function() {
    var currentPosition = window.pageYOffset;

    if (previousPosition > currentPosition) {
      remClass(navbar, 'headroom');
    } else if (currentPosition > navbarHeight) {
      addClass(navbar, 'headroom');
    }

    previousPosition = currentPosition;
};

Note that we will add headroom to the page only when the current position is greater than the navigation bar’s height.

else if (currentPosition > navbarHeight) {
      addClass(navbar, 'headroom');
}

For our CSS style sheet we will force the navigation bar upwards from its initial fixed position and give it a transition time to make the movement less jarring.

.headroom { top: -5em !important; }
.navbar {
  -webkit-transition: top 0.75s; 
  -o-transition: top 0.75s; 
  transition: top 0.75s; 
}

Demonstration and Source

Improvements

How can we improve this simple script? Let’s introduce velocity. We will show the navigation bar only if the user scrolls up faster than normal.

The velocity will be the difference between the previous and current position. The greater the difference, the faster the user scrolls. A scroll downwards produces a negative value and a scroll upwards produces a positive value.

var velocity = previousPosition - currentPosition;

Now that we know the velocity, we can add resistance by setting a velocity threshold of 55. The user must now overcome this velocity to reveal the navigation bar. The effect of this resistance threshold will vary based on device and browser.

Note that our else if is capped to velocity < 0. This handles a situation where the user performs a flick scroll gesture which involves both acceleration and deceleration. In this scenario accidental triggers can happen if we do not consider the direction of the scroll.

if (velocity > 55 || currentPosition < navbarHeight) {
	remClass(navbar, 'headroom');
} else if (velocity < 0) {
	addClass(navbar, 'headroom');
}

Due to this implementation, we will add an initial check at run time to handle a refresh anywhere in the middle of the This is an unnecessary check, but I did fancy the initial auto-hide on page load effect.

var previousPosition = window.pageYOffset;
var navbar = document.getElementById("navbar");
var navbarHeight = navbar.offsetHeight;

if (previousPosition > navbarHeight) {
    addClass(navbar, 'headroom');
}

window.onscroll = function() {
    var currentPosition = window.pageYOffset;
    var velocity = previousPosition - currentPosition;

    if (velocity > 55 || currentPosition < navbarHeight) {
      remClass(navbar, 'headroom');
    } else if (velocity < 0) {
      addClass(navbar, 'headroom');
    }

    previousPosition = currentPosition;
};

Demonstration and Source

Conclusion

That’s it. We can make even more improvements but this looks good enough.

Updated 8 April 2019