Skip to content

Instantly share code, notes, and snippets.

@andrewhathaway
Created September 14, 2012 23:18
Show Gist options
  • Save andrewhathaway/3725561 to your computer and use it in GitHub Desktop.
Save andrewhathaway/3725561 to your computer and use it in GitHub Desktop.
MD

How to write a cool CSS3 progress bar

I was set the task to write a progress bar at work recently, and I couldn't really think of a way to do it at first. After a while I came up with this. I'm going to show you how to write one and if you want to see a demo there is one here. We will start with a basic progress bar and extend it as we go on.

The way I did this is pretty simple to be honest. I started off with a div. This acts as your outer 'container' which you set to the width of your choice.

HTML

<div class="meter"></div>

CSS

div.meter {
	position: relative;
	width: 250px;
	height: 25px;
	border: 1px solid #b0b0b0;

	-webkit-box-shadow: inset 0 3px 5px 0 #d3d0d0;
	   -moz-box-shadow: inset 0 3px 5px 0 #d3d0d0;
	        box-shadow: inset 0 3px 5px 0 #d3d0d0;

	-webkit-border-radius: 3px;
	   -moz-border-radius: 3px;
	    -ms-border-radius: 3px;
	     -o-border-radius: 3px;
	        border-radius: 3px;
}

What does this do?

The position relative is used later so we can position some items inside the meter to absolute and not have them fly to the top of the page. The height and width are both entirely up to you, they just seem like a nice size for a progress bar. Onto the border, I will be keeping the borders at 1px throughout to make it match up, otherwise things wont "fit in". Next is some CSS3. Box-shadow makes things look pretty. It also makes the bar look slightly rounded, adds character. Or is that just me? The border radius property makes corners round, again making things pretty. These will not work in IE9 or less but your bar will still be functional.

Next to create the bar itself.

HTML - Continued

<div class="meter">
	<span style="width: 50%"></span>
</div>

I use the span for the bar. Above I've also used some in line CSS, I'm sorry. It's just a nice way to do things and data attributes did not seem to work for me. Using this in line CSS you can then just echo your data from PHP after working out a percentage. This will then fill the bar to the percentage suitable for the bar.

CSS

div.meter span {
	display: block;
	height: 100%;
	position: relative;
	top: -1px;
	left: -1px;

	border: 1px solid #3c84ad;

	-webkit-border-radius: 3px;
	   -moz-border-radius: 3px;
	    -ms-border-radius: 3px;
	     -o-border-radius: 3px;
	        border-radius: 3px;

	webkit-box-shadow: inset 0px 3px 5px 0 rgba(0, 0, 0, 0.2);
	  -moz-box-shadow: inset 0px 3px 5px 0 rgba(0, 0, 0, 0.2);
	       box-shadow: inset 0px 3px 5px 0 rgba(0, 0, 0, 0.2);

	background: #6eb2d1; 
}

Let's go through it.

At the start we have some sizing properties to deal with how the span fits within the container. As it's a "text element" we use display: block; so we can treat it like a div, as such. The height is set to 100% to fill the container. We then use position: relative; which allows us to position the element how we want. In this case we want the border of the span to go over the border of the container so we move the span back a pixel for the left and top.

As the container has a border radius of 3px, we don't want our bar to be squared so we match the border radius of the container for the span. Next up, the shadow. Adding a better feel to the element again. Makes it look a bit rounded, I think.

Then all we need to do is set the colour of the bar by adding the background property.

Gradient bar

background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, transparent), color-stop(100%, rgba(0, 0, 0, 0.2)));
background-image: -webkit-linear-gradient(transparent, rgba(0, 0, 0, 0.2));
background-image: -moz-linear-gradient(transparent, rgba(0, 0, 0, 0.2));
background-image: -o-linear-gradient(transparent, rgba(0, 0, 0, 0.2));
background-image: linear-gradient(transparent, rgba(0, 0, 0, 0.2));

The above CSS creates a background-image over the colour specified as the background in the CSS before. This background image goes from transparent to a darkened version of the colour behind by using RGBA(). This is a very simple gradient, from one colour to another spreading the whole width. This is done by having only 2 colour stops. 0% and 100%. This means that from 0% to 100% the colour will gradually get darker (from transparent to rgba(0, 0, 0, .2)).

Candy Striping

Yeah gradients are cool, but we can apply gradients to a further extent to create "Candy Striping". Let's see how its done.

background-image: -webkit-gradient(linear, 0 0, 100% 100%, color-stop(0.25, rgba(255, 255, 255, 0.2)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.2)), color-stop(0.75, rgba(255, 255, 255, 0.2)), color-stop(0.75, transparent), to(transparent)); 
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.2) 75%, transparent 75%, transparent); 
background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.2) 75%, transparent 75%, transparent); 
background-image: -ms-linear-gradient(45deg, rgba(255, 255, 255, 0.2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.2) 75%, transparent 75%, transparent);
background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.2) 75%, transparent 75%, transparent);

-webkit-background-size: 45px 45px;
   -moz-background-size: 45px 45px;
     -o-background-size: 45px 45px;
        background-size: 45px 45px;

This is where the cool bit comes in. We need to keep the background property from the original CSS. This is because we overlay lighter and darker colours on top of it to add stripes.

Using "45deg" it rotates the gradient so the stripes are not sideways. You can also use "-45deg" to have the stripes facing the other way. Color-stop() is a feature allowing you to stop or start a colour at a point given. color-stop() works in percentages, in this case every 25%. The gradient above has a stripe every 25% by stopping one colour and starting another. One stripe is completely transparent, so the background: #6eb2d1; we set before this chain is shown. The next stripe uses RGBA() to lighten the colour #6eb2d1. rgb(255, 255, 255) is white. Changing this to rgba(255, 255, 255, .2) makes the white have the opacity of 20%, lightening the colour behind it! Note that "linear-gradient" does not require the color-stop() function, but it is the same process with the seperate colours and percentages there. Each colour stop is specified by colour, then the percentage of which to start or stop and then they are seperate by commas.

We then simply set the background size to set the size of the stripes. Although, this has to be tuned because the stripes sometimes start to not match up.

So we now have quite a cool looking progress bar. It's candy striped, it works in percentages and is easily updateable to go with your data. However, we can start to go even further!

Extending the progress bar

If you looked at my example of what I wrote at the start of this post, you might have noticed it has a line through the middle, adding even more detail to the progress bar. This is something really simple to do. To do this we could use an extra div inside of the span but I like to use pseudo elements which are amazing! The HTML does not change, so lets extend this CSS.

div.meter span:before {
	content: '';
	display: block;
	width: 100%;
	height: 50%;
	position: relative;
	top: 50%;
	background: rgba(0, 0, 0, 0.03);
}

Lets run through. Pseudo elements wont appear without a content property, it's how it goes. We can just leave this one blank for now as we don't want any text displaying. Again we need to make it display: block; so we can add width and height properties, which I set to the full width of the bar and half of the height of the bar. I then used the top property to bump it down to the bottom. The background I then made darker by 2%, to create the line.

Now, why don't we notify the user of what percentage it actually is? Except, I want to keep it in the progress bar and not to the side of it.

HTML - continued

<div class="meter">
	<span style="width:72%"></span>
	<p>72%</p>
</div>

Just add a paragraph tag in the meter div but not in the span. Now for the CSS.

p {
	position: absolute;
	top: 0;
	margin: 0 10px;
	line-height: 25px;

	font-family: 'Helvetica';
	font-weight: bold;
	-webkit-font-smoothing: antialised;
	font-size: 15px;
	color: #333;
	text-shadow: 0 1px rgba(255, 255, 255, .6);
}

Just some basic CSS for text styling. I had a few issues with getting the text to align and sit properly so this is what I did. position to absolute, allowing us to set property top to 0. Then remove the margin from the paragraph, which hopefully will have been removed previously from a CSS reset and then line-height to the height of the progress bar. I also set a left margin of 10px to give the text space between the sides.

I then used some simple CSS to style the text in a reasonable way with a text shadow to make it pretty.

Now, this is where it gets even cooler. CSS3 animation. "On load" as such, we can make it grow. Now, I don't know about you, but animated elements make me happy, smoothing everything down! Luckily, it doesn't even take much code!

What we add

@keyframes grower {
	0% { width: 0%; }
}

/* Firefox */
@-moz-keyframes grower {
	0% { width: 0%; }
}

/* Safari and Chrome */
@-webkit-keyframes grower {
	0% { width: 0%; }
}

/* Opera */
@-o-keyframes grower {
	0% { width: 0%; }
}

We add this anywhere in the CSS file, this is our animation, our 'frames'. Repeated a few times for our vendor prefixes, so other browsers support this animation. We give our animation a name, "grower" I called it. This makes it so our animation will start at width: 0%; and end at the width we gave it previously from the in line CSS of the span. This is not all, we need to tell our span to run this animation. Simples! Add the following CSS to your your span.

-webkit-animation: grower 1s linear; 
   -moz-animation: grower 1s linear;
     -o-animation: grower 1s linear;
        animation: grower 1s linear;

Again we add a few lines of the same code but changed slightly for browser support. We then call the animation, this is the name we gave it above. Set a time for how long it occurs for and then give it an animation timing. Now, I'm not the best to ask about animation timings but linear keeps it at the same speed all the way through the animation, making it smooth. It may seem more realistic to have it change speeds if you're faking to load something, but in this case It's smooth.

Now, I know the title says CSS3 but why not go add some jQuery along with it. I want the current percentage to update as the bar loads, and keep in time. Now, I'm not too great with JS/jQuery but this worked.

//Store some variables
var bar = $('span');
var p = $('p');

//Get the width we want and remove the percentage sign
var width = bar.attr('style');
width = width.replace("width:", "");
width = width.substr(0, width.length-1);

//Variables for the loop
var interval;
var start = 0; 
var end = parseInt(width);
var current = start;

//The countUp function to update our paragraph tag.
var countUp = function() {
  current++;
  p.html(current + "%");
  
  if (current === end) {
    clearInterval(interval);
  }
};

//Run the function 72 times in a second.
interval = setInterval(countUp, (1000 / (end + 1)));

We start off my putting the elements we need into a variable, this is just what I've been taught to do for caching, it's also just nicer to deal with when it comes to 'acting' on them.

I then had a bit off a fuss getting the width we want for the variable "end" as the CSS as the animation was setting it to 0. However I got it by getting the style attribute from the span, removing "width:" and then using substr() to remove the percentage sign from the end. This will only work if you have just width in the style attribute, which it should be. Inline CSS is naughty!

The next bit makes the paragraph count up, it also keeps in time with the CSS animation. I set up some variables up that we need during the function. Variable start is what number to start counting from, and end is what number to stop at.

I had to convert the width variable to a Integer (int) otherwise it won't stop counting! the countUp() function just increments the current variable and sets the paragraphs text to the number suitable.

The if check is used to stop the counting when it hits that magic number (variable end). Then all we need to do is use setInterval() to make the function countUp() run 72 times in a second. 1000 ms is equal to one second, but we need to run this function 72 times in one second, so we do some basic maths to work out when to run it. One more thing, if you're using this jQuery script you can remove the percentage value from the paragraph and it will still work.

So there you have it. A cool, completely customisable yet useable progress bar ready for your website, or WebApp! Hope you enjoyed reading this tutorial. If you have any questions be sure to send me a tweet!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment