Scroll Progress Indicator With jQuery and SVG
we will be looking at a few techniques we can use to show scroll progress for users who are reading a page. This technique is being used on an increasing number of sites, and for good reason; it provides a contextual understanding of investment needed to consume a particular page.As the user scrolls, they are presented with a sense of current progress in different formats.
Setting up the Document
We will be using normalize.css and jQuery, as well as a Google font. Your empty HTML file should look like this:
<!doctype html>
<html>
<head>
<title>Progress Indicator Animation</title>
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<!-- fake post content goes here -->
<script src="js/jquery.min.js"></script>
<script src="js/script.js"></script>
</body>
</html>
Next, we will add our fake post content:
<main>
<article>
<header>
<h1>
<div class="container">
How Should We Show Progress While Scrolling a Post?
</div>
</h1>
</header>
<div class="article-content">
<h2 class="lead-in">
<div class="container">
Lorem ipsum dolor sit amet, consectetur adipisicing elit.
</div>
</h2>
<div class="container">
<p>this is the demo of any jquery for testing .</p>
<!-- add your own additional lorem here -->
</div>
</div>
</article>
<footer>
<h3 class="read-next"><small>Read Next:</small><br>How do I Implement a Foobar?</h3>
</footer>
</main>
Basic Styling
@import url(http://fonts.googleapis.com/css?family=Domine:400,700);
body {
font-size: 16px;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: "Domine", sans-serif;
}
h1 {
font-size: 3.5em;
}
.lead-in {
color: #fff;
font-weight: 400;
padding: 60px 0;
background-color: #0082FF;
}
article header {
border-top: 3px solid #777;
padding: 80px 0;
}
.article-content {
font-size: 1em;
font-weight: 100;
line-height: 2.4em;
}
p {
margin: 4em 0;
}
.container {
width: 700px;
margin: 0 auto;
}
footer {
text-align: center;
background-color: #666;
color: #fff;
padding: 40px 0;
margin-top: 60px;
}
.read-next {
font-size: 2em;
}
Scroll Position Calculation
we need to understand conceptually what we are tracking. Since JavaScript can track only the top scroll value, we will need to track our scroll value from 0 (at the top, not scrolled) to whatever the final scroll value is. That final scroll value will be equal to the total document length minus the height of the window itself (because the document will scroll until the bottom of the document reaches the bottom of the window).
We will use the following JavaScript to calculate this scroll position.
(function(){
var $w = $(window);
var wh = $w.height();
var h = $('body').height();
var sHeight = h - wh;
$w.on('scroll', function(){
var perc = Math.max(0, Math.min(1, $w.scrollTop()/sHeight));
});
}());
The above code sets the window height and the body height, and when the user scrolls it uses those values to set a perc variable (short for percentage). We also utilize Math.min and Math.max to limit the values to the 0-100 range.
Circle Indicator
The first indicator we will create is an SVG circle. We will utilize the SVG stroke-dasharray andstroke-dashoffset properties to show progress. First, let's add the progress indicator to the document.
<div class="progress-indicator">
<svg>
<g>
<circle cx="0" cy="0" r="20" class="animated-circle" transform="translate(50,50) rotate(-90)" />
</g>
<g>
<circle cx="0" cy="0" r="38" transform="translate(50,50)" />
</g>
</svg>
<div class="progress-count"></div>
</div>
This markup gives us two circles in an SVG, as well as a containing div to show our percentage count. We need to add style to these elements as well, and then we'll explain how these circles are positioned and animated.
.progress-indicator {
position: fixed;
top: 30px;
right: 30px;
width: 100px;
height: 100px;
}
.progress-count {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
text-align: center;
line-height: 100px;
color: #0082FF;
}
svg {
position: absolute;
}
circle {
fill: rgba(255,255,255,0.9);
}
svg .animated-circle {
fill: transparent;
stroke-width: 40px;
stroke: #0A74DA;
stroke-dasharray: 126;
stroke-dashoffset: 126;
}
The circles are positioned in the center using transform on the SVG elements themselves. We start the center of our circles using transform. We use a technique here that allows us to apply a rotation from the center of our circles in order to start the animation at the top of the circle (rather than the right side of the circle). In SVG, transforms are applied from the top left of an element. This is why we must center our circles at 0, 0, and move the circle's center to the center of the SVG itself using translate(50, 50).
Updating stroke-dasharray on Scroll
we will add a function to update the stroke-dasharray on scroll, using our percentage progress previously shown.
(function(){
var $w = $(window);
var $circ = $('.animated-circle');
var $progCount = $('.progress-count');
var wh = $w.height();
var h = $('body').height();
var sHeight = h - wh;
$w.on('scroll', function(){
var perc = Math.max(0, Math.min(1, $w.scrollTop()/sHeight));
updateProgress(perc);
});
function updateProgress(perc){
var circle_offset = 126 * perc;
$circ.css({
"stroke-dashoffset" : 126 - circle_offset
});
$progCount.html(Math.round(perc * 100) + "%");
}
}());
The offset that matches our circle happens to be about 126. It's important to note that this won't work for all circles, as 126 is about the circumference of a circle with a radius of 20. To calculate the stroke-dashoffset for a given circle, mutiply the radius by 2PI. In our case, the exact offset would be 20 * 2PI = 125.66370614359172.
Horizontal Progress Variation
we'll make a simple horizontal bar fixed to the top of the window. To accomplish this, we'll use an empty progress indicator div.
<div class="progress-indicator-2"></div>
we'll add our styling for this element.
.progress-indicator-2 {
position: fixed;
top: 0;
left: 0;
height: 3px;
background-color: #0A74DA;
}
we will set the width of the progress bar on scroll.
var $prog2 = $('.progress-indicator-2');
function updateProgress(perc){
$prog2.css({width : perc*100 + '%'});
}
our final JavaScript
(function(){
var $w = $(window);
var $circ = $('.animated-circle');
var $progCount = $('.progress-count');
var $prog2 = $('.progress-indicator-2');
var wh = $w.height();
var h = $('body').height();
var sHeight = h - wh;
$w.on('scroll', function(){
var perc = Math.max(0, Math.min(1, $w.scrollTop()/sHeight));
updateProgress(perc);
});
function updateProgress(perc){
var circle_offset = 126 * perc;
$circ.css({
"stroke-dashoffset" : 126 - circle_offset
});
$progCount.html(Math.round(perc * 100) + "%");
$prog2.css({width : perc*100 + '%'});
}
}());
Other Ideas for Progress Bars
Other ideas for progress bars might include using more descriptive or humanized terms for the progress indication itself, such as "halfway there" or "just getting started".
var wordsPerMin = 300; // based on this article: http://www.forbes.com/sites/brettnelson/2012/06/04/do-you-read-fast-enough-to-be-successful/
var wordsArray = $(".article-content").text().split(" ");
var wordCount = wordsArray.length;
var minCount = Math.round(wordCount / wordsPerMin);
Here's a very basic implementation of this concept.
function updateProgress(perc){
var minutesCompleted = Math.round(perc * minCount);
var remaining = minCount - minutesCompleted;
if (remaining){
$(".progress-indicator").show().html(remaining + " minutes remaining");
} else {
$(".progress-indicator").hide();
}
}
One Final Piece: Adaptive Screen Sizing
(function(){
var $w = $(window);
var $circ = $('.animated-circle');
var $progCount = $('.progress-count');
var $prog2 = $('.progress-indicator-2');
var wh, h, sHeight;
function setSizes(){
wh = $w.height();
h = $('body').height();
sHeight = h - wh;
}
setSizes();
$w.on('scroll', function(){
var perc = Math.max(0, Math.min(1, $w.scrollTop()/sHeight));
updateProgress(perc);
}).on('resize', function(){
setSizes();
$w.trigger('scroll');
});
function updateProgress(perc){
var circle_offset = 126 * perc;
$circ.css({
"stroke-dashoffset" : 126 - circle_offset
});
$progCount.html(Math.round(perc * 100) + "%");
$prog2.css({width : perc*100 + '%'});
}
}());
This code declares a function which sets the variables we need to calculate the progress at any given screen size, and calls that function on resize. We also re-trigger scroll on window resize so that our updateProgress function is executed.
