Progress bars
have become quite popular lately, and there are lots of plugins that
can help you add one to your site. But how do you make one yourself? The
problem is that there are plenty of implementations already, so in this
tutorial, we are going to code something different – buttons that have
built in progress meters.
DEMO
They will be perfect for displaying progress while submitting forms
or loading content via AJAX. They will also use CSS3 styles and
transitions to make them easy to customize.
The HTML
In the first section of the tutorial, we will write the HTML markup.
It comes in the form of a standard HTML5 document which includes two
additional resources that we will discuss later – the styles.css stylesheet and the script.js JavaScript file. Additionally, I have included the jQuery library, and the Raleway font from Google web fonts.
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Tutorial: Buttons with built-in progress meters</title>
<link href="http://fonts.googleapis.com/css?family=Raleway:400,700" rel="stylesheet" />
<link href="assets/css/style.css" rel="stylesheet" />
</head>
<body>
<h1>Progress Buttons</h1>
<a id="submitButton" href="#" class="progress-button">Submit</a>
<a id="actionButton" href="#" class="progress-button green" data-loading="Working.." data-finished="Finished!" data-type="background-bar">Action!</a>
<a id="generateButton" href="#" class="progress-button red" data-loading="Generating.." data-finished="Download" data-type="background-vertical">Generate</a>
<h1>Progress Control</h1>
<a id="controlButton" href="#" class="progress-button">Start</a>
<div class="control-area">
<a class="command increment">Increment</a>
<a class="command set-to-1">Set to 1%</a>
<a class="command set-to-50">Set to 50%</a>
<a class="command finish">Finish</a>
</div>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="assets/js/script.js"></script>
</body>
</html>
The markup is pretty simple. The progress buttons are defined as
regular hyperlinks. In order to be recognized by the plugin and turned
into buttons with built-in progress bars, they need to have the .progress-button class. Buttons can also be configured by setting three data-* attributes:
data-type specifies what type of progress bars will be shown. Currently three types are supported: background-horizontal (the default), background-bar and background-vertical.
data-loading specifies the text that is shown while the progress bar is moving. The default value is Loading..
data-finished holds the text that is set on the button when the progress is done. The default value is Done!
If you omit an attribute, the default value will be used.

Buttons With Built-in Progress Meters
The jQuery Code
In this part of the tutorial, we are going to write the JavaScript
and jQuery code to make the buttons work. The code is organized as 6
jQuery plugins that share a common name – progressInitialize, progressStart, progressIncrement, progressTimed, progressSet and progressFinish. I have provided plenty of comments in the code, so you can dig right in:
assets/js/script.js
$(document).ready(function(){
$('.progress-button').progressInitialize();
$('#submitButton').click(function(e){
e.preventDefault();
$(this).progressTimed(2);
});
$('#actionButton').click(function(e){
e.preventDefault();
$(this).progressTimed(2);
});
$('#generateButton').one('click', function(e){
e.preventDefault();
var button = $(this);
button.progressTimed(3, function(){
button.click(function(){
alert('Showing how a callback works!');
});
});
});
var controlButton = $('#controlButton');
controlButton.click(function(e){
e.preventDefault();
controlButton.progressStart();
});
$('.command.increment').click(function(){
controlButton.progressIncrement();
});
$('.command.set-to-1').click(function(){
controlButton.progressSet(1);
});
$('.command.set-to-50').click(function(){
controlButton.progressSet(50);
});
$('.command.finish').click(function(){
controlButton.progressFinish();
});
});
(function($){
$.fn.progressInitialize = function(){
return this.each(function(){
var button = $(this),
progress = 0;
var options = $.extend({
type:'background-horizontal',
loading: 'Loading..',
finished: 'Done!'
}, button.data());
button.attr({'data-loading': options.loading, 'data-finished': options.finished});
var bar = $('<span class="tz-bar ' + options.type + '">').appendTo(button);
button.on('progress', function(e, val, absolute, finish){
if(!button.hasClass('in-progress')){
bar.show();
progress = 0;
button.removeClass('finished').addClass('in-progress')
}
if(absolute){
progress = val;
}
else{
progress += val;
}
if(progress >= 100){
progress = 100;
}
if(finish){
button.removeClass('in-progress').addClass('finished');
bar.delay(500).fadeOut(function(){
button.trigger('progress-finish');
setProgress(0);
});
}
setProgress(progress);
});
function setProgress(percentage){
bar.filter('.background-horizontal,.background-bar').width(percentage+'%');
bar.filter('.background-vertical').height(percentage+'%');
}
});
};
$.fn.progressStart = function(){
var button = this.first(),
last_progress = new Date().getTime();
if(button.hasClass('in-progress')){
return this;
}
button.on('progress', function(){
last_progress = new Date().getTime();
});
var interval = window.setInterval(function(){
if( new Date().getTime() > 2000+last_progress){
button.progressIncrement(5);
}
}, 500);
button.on('progress-finish',function(){
window.clearInterval(interval);
});
return button.progressIncrement(10);
};
$.fn.progressFinish = function(){
return this.first().progressSet(100);
};
$.fn.progressIncrement = function(val){
val = val || 10;
var button = this.first();
button.trigger('progress',[val])
return this;
};
$.fn.progressSet = function(val){
val = val || 10;
var finish = false;
if(val >= 100){
finish = true;
}
return this.first().trigger('progress',[val, true, finish]);
};
$.fn.progressTimed = function(seconds, cb){
var button = this.first(),
bar = button.find('.tz-bar');
if(button.is('.in-progress')){
return this;
}
bar.css('transition', seconds+'s linear');
button.progressSet(99);
window.setTimeout(function(){
bar.css('transition','');
button.progressFinish();
if($.isFunction(cb)){
cb();
}
}, seconds*1000);
};
})(jQuery);
progressInitialize sets up an event listener for the
progress custom event that the other functions call whenever the meter
needs to be updated. Thanks to the custom event, we can have entirely
independent functions like progressStart, which manages its own timer and state – progresInitialize need not know about progressStart.
The other important thing is that we are setting two special classes on the buttons – .in-progress while the progress meter is moving, and .finished when it’s ready. They are used to update the text of the buttons as you will see in the next section.
The CSS
I mentioned that we are setting two CSS classes on the buttons – .in-progress and .finished.
But how does adding one of these classes change the text of the button?
Simple – we are using a CSS trick that involves the CSS3 attr operator, which when combined with content, can set the text of a :before or :after pseudo element to that of the attribute of the element. It will become clearer once you see it for yourself (lines 44-52):
assets/css/styles.css
.progress-button{
display: inline-block;
font-size:24px;
color:#fff !important;
text-decoration: none !important;
padding:14px 60px;
line-height:1;
overflow: hidden;
position:relative;
box-shadow:0 1px 1px #ccc;
border-radius:2px;
background-color: #51b7e6;
background-image:-webkit-linear-gradient(top, #51b7e6, #4dafdd);
background-image:-moz-linear-gradient(top, #51b7e6, #4dafdd);
background-image:linear-gradient(top, #51b7e6, #4dafdd);
}
.progress-button.in-progress,
.progress-button.finished{
color:transparent !important;
}
.progress-button.in-progress:after,
.progress-button.finished:after{
position: absolute;
z-index: 2;
width: 100%;
height: 100%;
text-align: center;
top: 0;
padding-top: inherit;
color: #fff !important;
left: 0;
}
.progress-button.in-progress:after{
content:attr(data-loading);
}
.progress-button.finished:after{
content:attr(data-finished);
}
.progress-button .tz-bar{
background-color:#e667c0;
height:3px;
bottom:0;
left:0;
width:0;
position:absolute;
z-index:1;
border-radius:0 0 2px 2px;
-webkit-transition: width 0.5s, height 0.5s;
-moz-transition: width 0.5s, height 0.5s;
transition: width 0.5s, height 0.5s;
}
.progress-button .tz-bar.background-horizontal{
height:100%;
border-radius:2px;
}
.progress-button .tz-bar.background-vertical{
height:0;
top:0;
width:100%;
border-radius:2px;
}
The rest of the code styles the buttons and the built-in progress meter. In styles.css I
have also included two additional color themes and some other rules
that are not given here, but which you can see by browsing the source
yourself.
We’re Done!
The code we wrote today is ready for customizations. Simply open
styles.css in your favorite code editor and change the colors, fonts or
styles so they match your site. Change the text by editing the HTML and
the data attributes. Or you can even improve the code with some cool new
feature. In this case be sure to share it in the comment section :)