Sticky Reading Progress Nav Bar With Smooth Scroll Support

Sticky Reading Progress Nav Bar With Smooth Scroll Support
File Size: 4.82 KB
Views Total:
Last Update:
Publish Date:
Official Website: Go to website
License: MIT
   

A fancy responsive sticky reading progress bar to indicate how much of the main content has been read based on a user's scroll position. Written in jQuery JavaScript library.

By default, the script walks through your heading elements (h2) and generates a sectioned progress bar that auto updates as you scroll down the webpage.

Also can be used as a sticky navigation bar that allows the user to scroll between content sections by clicking on the nodes inside the reading progress bar.

How to use it:

1. Create sectioned content separated with <h2> tag as these:

<h2>Section One</h2>
<p>Section 1 Content</p>

<h2>Section Two</h2>
<p>Section 2 Content</p>

<h2>Section Three</h2>
<p>Section 3 Content</p>

...

2. The main JavaScript (jQuery script) to create a reading progress bar and make it sticky on the top of the webpage on scroll. Copy and add the followng JS snippets after jQuery.

// set up and create progress bar in DOM
$('h2').eq(0).before('<div class="progressbar"></div>');
var container = $('.progressbar');
container.append('<div class="shim"></div>'); 
var shim = $('.progressbar .shim');
container.append('<div class="holder clearfix"></div>');
var holder = $('.progressbar .holder');
holder.append('<div class="bar"></div>');
var bar = $('.progressbar .bar');
bar.append('<div class="indicator"></div>');
var indicator = $('.progressbar .indicator');
holder.append('<div class="labels"></div>');
var labels = $('.progressbar .labels');
$('h2').each(function(){
  var code = '<i data-label="'+$(this).text()+'"></i>';
  labels.append(code);
});
var points = labels.find('i');
points.css('width', 100/$('h2').length+'%');

// match height of shim
// stop layout jumping when progress bar fixes to / unfixes
// from top of viewport
function setShimHeight(){
  shim.css('height', container.height()+'px');
}
setShimHeight();
$(window).resize(function(){ setShimHeight(); });

// position indicator bar so it starts at first dot
function setIndicatorX(){
  var point = points.eq(0);
  var xpos = point.offset().left + (point.width() / 2);
  indicator.css('left', xpos+'px');
}
setIndicatorX();
$(window).resize(function(){ setIndicatorX(); });

// fix/unfix progress bar to top of viewport
function fixPosition(){
  if(container.is(':visible')) {
    if(!container.hasClass('fixed')) {
      if(holder.offset().top <= $(window).scrollTop()) {
        container.addClass('fixed');
      }
    }
    else {
      if(shim.offset().top > $(window).scrollTop()) {
        container.removeClass('fixed');
      }
    }
  }
}
fixPosition();
$(window).scroll(function(){ fixPosition() });
$(window).resize(function(){ fixPosition(); });

// set trigger point
// i.e. how far down viewport is the "eye line"
var triggerPoint = 0;
function setTriggerPoint(){
  triggerPoint = $(window).height() * .18;
}
setTriggerPoint();
$(window).resize(function(){ setTriggerPoint(); });

// update progress bar
function setPosition(){
  if(container.is(':visible')) {
    var section = false;
    var sectionIndex = 0;
    var currentPosition = $(window).scrollTop() + triggerPoint;
    // dots
    // if before first section
    if(currentPosition < $('h2').eq(0).offset().top) {
      points.removeClass('reading read');
      section = -1;
    }
    // if after first section
    else {
      $('h2').each(function(){
        var sectionTop = $(this).offset().top;
        if(currentPosition >= sectionTop) {
          points.removeClass('reading');
          points.eq(sectionIndex).addClass('reading');
          points.eq(sectionIndex).addClass('read');
          section = sectionIndex;
        }
        else {
          points.eq(sectionIndex).removeClass('read');
        }
        sectionIndex++;
      });
    }
    // bar
    var barWidth = 0;
    // if before start
    if(section == -1) {
      var point = points.eq(0);
      barWidth = point.offset().left + (point.width() / 2);
    }
    // if after end
    else if(section >= (points.length - 1)) {
      var point = points.eq((points.length - 1));
      barWidth = point.offset().left + (point.width() / 2);
    }
    // if within document
    else {
      var startPoint = points.eq(section);
      var startPointX = startPoint.offset().left;
      var startPointWidth = startPoint.width();
      var startSection = $('h2').eq(section);
      var endSection = $('h2').eq(section+1);
      var startSectionY = startSection.offset().top;
      var endSectionY = endSection.offset().top;
      var sectionLength = endSectionY - startSectionY;
      var scrollY = currentPosition - startSectionY;
      var sectionProgress = scrollY / sectionLength;
      barWidth = startPointX + (startPointWidth / 2) + (startPointWidth * sectionProgress);
    }
    barWidth -= indicator.offset().left;
    indicator.css('width', barWidth+'px');
  }
}
setPosition();
$(window).scroll(function(){ setPosition(); });
$(window).resize(function(){ setPosition(); });

3. The JavaScript to activate the smooth scroll functionality.

points.click(function(){
  var sectionIndex = points.index($(this));
  var targetY = $('h2').eq(sectionIndex).offset().top - (triggerPoint * .92);
  $('html, body').animate({scrollTop:targetY}, 600);
});

4. The generated HTML of the reading progress bar should be like this:

<div class="progressbar fixed">
  <div class="shim" style="height: 85.6563px;"></div>
  <div class="holder clearfix">
    <div class="bar">
      <div class="indicator" style="left: 112.133px; width: 358.257px;"></div>
    </div>
    <div class="labels">
      <i data-label="Section One" style="width: 16.6667%;" class="read"></i>
      <i data-label="Section Two" style="width: 16.6667%;" class="read"></i>
      <i data-label="Section Three" style="width: 16.6667%;" class="read reading"></i>
      <i data-label="Section Four" style="width: 16.6667%;"></i>
      <i data-label="Section Five" style="width: 16.6667%;"></i>
      <i data-label="Section Six" style="width: 16.6667%;"></i></div>
  </div>
</div>

5. Style the reading progress bar using the following CSS snippets.

.progressbar {
  display: none;
  margin: 4em 0;
}

@media only screen and (min-width: 650px) {

.progressbar { display: block; }
}

.progressbar .shim {
  display: none;
  width: 100%;
}

.progressbar .holder {
  position: relative;
  font-size: 85%;
  padding: 1.8em 0 0 0;
  background-color: #D6E1E5;
  box-shadow: 0 0.5em 1.5em #D6E1E5;
}

@media only screen and (min-width: 750px) {

.progressbar .holder { font-size: 90%; }
}

@media only screen and (min-width: 900px) {

.progressbar .holder { font-size: 95%; }
}

.progressbar .holder .bar {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 2px;
  background-color: #B6D1DA;
}

.progressbar .holder .bar .indicator {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  background-color: #4598B5;
}

.progressbar .holder .labels {
  max-width: 1280px;
  margin: 0 auto;
  padding: 0 2em;
  text-align: center;
}

.progressbar .holder .labels i {
  display: block;
  position: relative;
  float: left;
  cursor: pointer;
}

.progressbar .holder .labels i::before {
  position: absolute;
  bottom: 0;
  left: 50%;
  display: block;
  content: '';
  width: .9em;
  height: .9em;
  border-radius: 50%;
  border: solid 3px #B6D1DA;
  background-color: #D6E1E5;
  -webkit-transform: translateX(-50%) translateY(50%);
  transform: translateX(-50%) translateY(50%);
  transition: border-color 100ms ease-in, background-color 150ms ease-in;
}

.progressbar .holder .labels i::after {
  display: block;
  content: attr(data-label);
  position: relative;
  top: 0;
  padding-bottom: 1.8em;
  font-family: 'Open Sans';
  font-weight: 400;
  color: #4598B5;
  transition: color 150ms ease-in, top 100ms ease-out;
}

.progressbar .holder .labels i:hover::before, .progressbar .holder .labels i:focus::before { background-color: #B6D1DA; }

.progressbar .holder .labels i:hover::after, .progressbar .holder .labels i:focus::after { top: -.2em; }

.progressbar .holder .labels i.read::before { border-color: #4598B5; }

.progressbar .holder .labels i.read:hover::before, .progressbar .holder .labels i.read:focus::before { background-color: #4598B5; }

.progressbar .holder .labels i.reading::after { color: #222; }

.progressbar .holder .labels i.reading:hover::after, .progressbar .holder .labels i.reading:focus::after { top: 0; }

.progressbar.fixed .holder {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 1;
}

.progressbar.fixed .shim { display: block; }

This awesome jQuery plugin is developed by Ed Hicks. For more Advanced Usages, please check the demo page or visit the official website.