Scrolling Sticky Header

In the last few years, fixed headers have become a popular UI trend in web design. More recently, the fixed header has evolved to react to the act of scrolling – sliding up out of view when a user scrolls down and sliding back in from the top of the page when a user scrolls up. I like to call this UI pattern the scrolling sticky header. Here it is in action…

screengrab

I first saw the scrolling sticky header on the (not long for this world) Teehan Lax site and reversed engineered it for this little site you are reading now. Having implemented it on a number of projects since, I’ve finally gotten around to packaging it up as a minimal js module that can be easily dropped into a project.

How To

The module works by listening to the window scroll event and comparing the current window scroll position with the previous position – figuring out if a user is scrolling up or down. If we are scrolling down and our header is visible we add a class that triggers a CSS transition which translates the header along the Y axis out of sight off the top of the page.


.header--hidden {
  opacity: 0;
  transform: translate(0, -60px);
  transition: transform .2s,background .3s,color .3s,opacity 0 .3s;
}

If we are scrolling down, a different class is added that reverses that translation.



.header--visible {
  background: rgba(255,255,255,0.7);
  opacity: 1;
  transition: transform .3s, height .3s, background .4s, opacity .3s;
  transform: translate(0,0);
  position: fixed;
}

Because the scroll event is fired a shed load of times (depending on your browser), doing the computation to figure out if we are going up or down on every call could cause performance problems, particularly on mobile or lower powered devices.

Taking inspiration from this article on faster animations with RequestAnimationFrame, our first performance optimisation is to decouple updating the header from the scroll event. We have a separate callback function, _onScroll, for the scroll event and all this function is responsible for doing is updating our internal records of the current and previous scroll positions, and requesting an update to the header, via _requestTick.

/**
 * kick off
 */
StickyHeader.prototype._init = function() {

    // attach scroll event to window
    this.$window.on('scroll', this._onScroll.bind(this));

};

/**
 * scroll handler
 * updates perviousY record and sets latestY record to current scroll position
 * calls requestTick
 */
StickyHeader.prototype._onScroll = function() {
    this.previousY = this.latestScrollY;
    this.latestScrollY = this._getY(); // current scroll position
    this._requestTick();
};

Our second performance optimisation is to limit the number of calls to our update function. Rather than calling _update every time the scroll event is fired, we first check if an update is currently taking place, if so we don’t queue up a load of unnecessary requestAnimationFrame calls – we only call it when the last update is complete.

/**
 * checks whether an update is currently taking place, if not calls an update
 * via requestAnimationFrame
 */
StickyHeader.prototype._requestTick = function() {
    if (!this.ticking) {
        requestAnimationFrame(this._update.bind(this));
    }

    this.ticking = true;
};

Because our update function is likely to cause a repaint we wrap it in a requestAnimationFrame, meaning the browser will batch repaints and make them when it is good and ready.

Aside from polyfills for requestAnimaytionFrame, Function.bind() and a cross browser method for working out the current window scroll position, that’s pretty much all there is to the module.

Links

  1. Demo
  2. GitHub repo
  3. Optimising Animations with RequestAnimationFrame

One Response so far.

  1. Abhishek says:

    Great technique! Thanks for making the module. If you can compress it and put it in GitHub directly then it would be really great. I had to use the one from the demo page.

    I am redesigning my blog with Material theme and probably this little effect will add a nice animation. Thanks again.

Leave a Reply