Tuesday, August 16, 2011

CSS3 Transitions and JavaScript-powered Progress Bar

CSS3 brings web developers a large number of new ways to streamline web applications and reduce dependency on JavaScript. One such improvement is the new "transition" property, which makes changes to defined CSS properties more graceful.

One of the most tedious things to program in JavaScript is animation. It's not difficult, but there are many speed bumps: Should I use a timeout or an interval? What happens if the same CSS property begins an animation when an animation on that CSS property is already taking place?

The CSS3 transition property allows us an easy way to make smooth transitions while letting the browser makers worry about the hurdles. When I first read about this development, I could not believe how easy it is to use. Just add the following declaration to your CSS with the appropriate vendor prefixes (-webkit-, -moz-, -o-, or -ms-):

transition: top 1s ease;

…and whenever the "top" property of that element changes, it will be animated over a period of 1 second!

One practical application of using CSS3 transitions for animation is in a progress bar. When the value of a progress bar changes, it increases in width. Wouldn't it be nice if, as it increases in width, if it did so with a smooth animation rather than jumping straight to its destination? This can be done by animating the CSS width property.

In order to demonstrate this, I wrote up a progress bar JavaScript class. It's relatively simple to use: create an instance of the class, set the initial value, the maximum value, and then specify an element to which to append the progress bar object. Here is the JavaScript source of my progress bar class:

/* Progress Bar class by Vote 539
 * http://vote539-webdev.blogspot.com/
 *
 * Copyright 2011 Vote 539
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * For a copy of the GNU General Public License, see
 * <http://www.gnu.org/licenses/>.
 */
function Vote539_ProgressBar(){
	var width = 500;
	var height = 22;
	var objects = {};
	var max=1, value=0;
	var baseText = "";
	
	var _redraw = function(){
		if(objects.main){
			objects.fluid.innerHTML = "";
			objects.text.innerHTML = "";
			objects.main.innerHTML = "";
			objects.main.parentNode.removeChild(objects.main);
		}
		
		objects.main = document.createElement("div");
		objects.main.className = "vote539-progressbar";
		objects.main.style.width = width+"px";
		objects.main.style.height = height+"px";
		
		objects.fluid = document.createElement("div");
		objects.fluid.className = "vote539-pb-fluid";
		
		objects.text = document.createElement("div");
		objects.text.className = "vote539-pb-text";
		objects.text.style.lineHeight = height+"px";
		objects.text.style.fontSize = Math.round(height*.6)+"px";
		
		objects.main.appendChild(objects.fluid);
		objects.main.appendChild(objects.text);
	}
	
	var _setfluid = function(){
		if(!objects.main) _redraw();
		var percentage;
		if(max<=0) percentage=0;
		else if(value<0) percentage=0;
		else if(value>=max) percentage=1;
		else percentage=value/max;
		objects.fluid.style.width = Math.round(percentage*width)+"px";
		objects.text.innerHTML = baseText+Math.round(percentage*100)+"%";
	}
	
	this.appendTo = function(target)
		{ if(!objects.main) _redraw(); target.appendChild(objects.main); }
	this.jump = function()
		{ if(!objects.main)return; if(objects.main.parentNode) var parent=objects.main.parentNode; _redraw(); _setfluid(); if(parent) parent.appendChild(objects.main); }
	this.setWidthHeight = function(newwidth, newheight)
		{ if(newwidth) width=newwidth; if(newheight) height=newheight; _redraw(); _setfluid(); }
	this.setValueMax = function(newvalue, newmax)
		{ value=newvalue; max=newmax; _setfluid(); }
	this.setValue = function(newvalue)
		{ value=newvalue; _setfluid(); }
	this.setMax = function(newmax)
		{ max=newmax; _setfluid(); }
	this.getValue = function()
		{ return value; }
	this.getMax = function()
		{ return max; }
	this.setBaseText = function(newbasetext)
		{ baseText=newbasetext; _setfluid(); }
}

And here is the CSS that goes along:

.vote539-progressbar{
	position:relative;
	border:1px solid #1B8274;
	
	background-color:#FFDCD1;
	
	/* Original WebKit Syntax for Gradients */
	background-image:-webkit-gradient(linear,
		left top,
		left bottom,
		from(#FFDCD1),
		color-stop(50%, #FFE9E3),
		to(#FFDCD1));
	/* More Recent Syntax for Gradients */
	background-image:-webkit-linear-gradient(
		top,
		#FFDCD1 0%,
		#FFE9E3 50%,
		#FFDCD1 100%);
	background-image:-moz-linear-gradient(
		top,
		#FFDCD1 0%,
		#FFE9E3 50%,
		#FFDCD1 100%);
	background-image:-o-linear-gradient(
		top,
		#FFDCD1 0%,
		#FFE9E3 50%,
		#FFDCD1 100%);
	background-image:-ms-linear-gradient(
		top,
		#FFDCD1 0%,
		#FFE9E3 50%,
		#FFDCD1 100%);
}
.vote539-pb-fluid{
	position:absolute;
	top:0px;
	left:0px;
	height:inherit;
	
	/* Here is where we declare the CSS3 Transitions.
	 * We have one line for each browser prefix, plus
	 * the future standard at the end.
	 */
	-webkit-transition:width .6s ease; /* WebKit (3.2+) Version */
	-moz-transition:width .6s ease; /* Firefox (4+) Version */
	-o-transition:width .6s ease; /* Opera (10.5+) Version */
	-ms-transition:width .6s ease; /* Internet Explorer (10+?) Version */
	transition:width .6s ease; /* Future Standard */
	
	background-color:#3BD1BD;
	
	background-image:-webkit-gradient(linear,
		left top,
		left bottom,
		from(#2CB8A5),
		color-stop(50%, #3BD1BD),
		to(#2CB8A5));
	background-image:-webkit-linear-gradient(
		top,
		#2CB8A5 0%,
		#3BD1BD 50%,
		#2CB8A5 100%);
	background-image:-moz-linear-gradient(
		top,
		#2CB8A5 0%,
		#3BD1BD 50%,
		#2CB8A5 100%);
	background-image:-o-linear-gradient(
		top,
		#2CB8A5 0%,
		#3BD1BD 50%,
		#2CB8A5 100%);
	background-image:-ms-linear-gradient(
		top,
		#2CB8A5 0%,
		#3BD1BD 50%,
		#2CB8A5 100%);
}
.vote539-pb-text{
	position:absolute;
	top:0px;
	left:0px;
	width:inherit;
	height:inherit;
	
	font-family:Verdana, Arial, sans-serif;
	
	color:#00241F;
	text-align:center;
	vertical-align:middle;
}

Pay special attention to lines 42-50 in the CSS code. This is where the CSS3 transitions come into play!

Begin by declaring a new instance of the Vote539_ProgressBar class. Then, you need to call at least one method to set the starting value and the maximum value: setValueMax(). In most cases, you will also want to call the setWidthHeight() method to specify the width and height of the progress bar, in pixels. (It defaults to 500px wide and 22px tall.) Then, when you're ready, call the attachTo() method, passing it a reference to the element to which you want to append the progress bar. For example:

<div id="container" style="position:relative;"></div>
<script type="text/javascript">
var myProgressBar = new Vote539_ProgressBar();
myProgressBar.setWidthHeight(200, 18);
myProgressBar.setValueMax(5, 15);
myProgressBar.appendTo(document.getElementById("container"));
</script>

To update the value of the progress bar after it has been appended to the HTML document, simply call the setValue() method. The progress bar will adjust to the newly specified value, and, guess what: the progress bar will animate smoothly: all without using JavaScript-based animations!

myProgressBar.setValue(10);

Additional Features of the Vote539_ProgressBar Class

  • Call the jump() method if you are setting a new value for your progress bar and want to skip the transition. For example:
    myProgressBar.setValue(12);
    myProgressBar.jump();
  • By default, only the current percentage will appear as text over the progress bar. You may also specify your own text that will be prepended to the percentage. To do this, call the setBaseText method:
    myProgressBar.setBaseText("Downloading Percent Complete: ");

Live Demo

With the Vote 539 Progress Bar, everyone using a somewhat-modern browser will be able to see the information, and those using a browser with support for CSS3 Transitions will have the added bonus of the progress bar being animated!