Animating Personal Skill Bars With CSS3 Keyframes

This design technique is commonly found on personal portfolios or design studio websites. Skill bars represent a level of knowledge related to certain tasks – web design, illustration, branding, character design, you name it! Adding some fancy animations to these skill bars will provide a quicker connection to the viewer.

In this tutorial I want to demonstrate how you can build CSS3 skill bars using keyframe animation. This is also completely possible to run using JavaScript, which would hold up stronger in older legacy web browsers. However CSS3 keyframes are growing in popularity with much wider support these days. Take a peek at my live demo to see the final product.

2 Million+ Digital Assets, With Unlimited Downloads

Get unlimited downloads of 2 million+ design resources, themes, templates, photos, graphics and more. Envato Elements starts at $16 per month, and is the best creative subscription we've ever seen.

Explore Design Resources

Live DemoDownload Source Code

Getting Started

While browsing through Google I stumbled onto this post, outlining the CSS3 animation style. It’s actually pretty simple once you understand properties and how the animation/keyframes work together. My page uses a simple HTML5 doctype with a single external CSS stylesheet.

First we should take a look at the HTML markup and how this constructs an active dynamic bar. The page includes two different sections – one with inner labels and one without labels. Since all the skill bars use almost identical code, I’ve copied over just the first bar on the page.

  <div id="w">
    <div id="content">
      <h1>CSS3 Keyframe Skill Bars</h1>
      <p>Check out this visual representation of my current skillset.</p>
      
      <div id="skillset">
        <h2>Animated w/ Labels</h2>
        <div class="skill">
          <h3>HTML5/CSS3 <sup>(100%)</sup></h3>
          <span class="bar"><span class="skillbar htmlcss">8 yrs</span></span>
        </div>

The div #skillset is used to encapsulate the entire collection of skills. This behaves like a wrapper so we could center everything, increase/decrease the width, or adjust other settings in CSS. The inner div with a class .skill wraps around each individual skill being measured. In this case we’re looking at HTML5/CSS3 coding which is ranked at 100% knowledge.

The inner H3 header is in place so the viewer can tell what the skill is measuring. This could be placed inside the skill bar instead, but I chose to put the years of experience inside the bar. You’ll notice the animated bar itself is contained within an outer shell.

The span element .bar uses a static grey background with some box shadow effects to stand out on the page. It was designed in a similar fashion as the CSS Deck example, but I’ve tried to simplify the HTML a bit. The inner span .skillbar is where the animation happens.

Animate Individual Skills

You’ll notice each .skillbar also has a secondary class related to the skill being measured. These are attached to CSS3 animations which run over keyframes. Each skill is measured separately and they each stop at a different point (HTML/CSS just happens to reach 100%).

Since I am using classes for these skill bars we can actually duplicate them many times on the page. This is how I created another set of examples on the same page – by reusing the same animated classes. Since these bars also contain small bits of text, they can’t start at 0px width. Normally this is fine but in this case the text would break onto the next line, and only once the bar animates would the text reposition itself.

So having individual classes means I can setup the original width to match internal text length. This way it never breaks or appears buggy, and the text always stays centered in the skill bar no matter how wide it animates.

Positioning the Elements

My basic CSS properties aren’t too complicated but there are some ideas worth discussing. I’ll start with the overall structure of the skill bars container.

/** skills bar ui **/
#skillset {
  display: block;
  margin-bottom: 8px;
}

#skillset .skill { 
  display: block;
  margin-bottom: 20px;
}

#skillset .skill h3 {
  display: block;
  font-size: 1.55em;
  line-height: 1.4em;
  margin-bottom: 2px;
}

#skillset .skill .bar {
  display: block;
  height: 25px;
  line-height: 25px;
  width: 500px;
  padding: 3px 4px;
  -webkit-box-sizing: content-box;
  box-sizing: content-box;
  -moz-box-sizing: content-box;
  -webkit-border-radius: 6px;
  -moz-border-radius: 6px;
  border-radius: 6px;
  -webkit-box-shadow: 1px 1px 2px #969492;	
  -moz-box-shadow: 1px 1px 2px #969492;
  box-shadow: 1px 1px 2px #969492;
  background-color: #dadada;
  background-image: -webkit-gradient(linear, left top, left bottom, from(#efeaea), to(#dadada));
  background-image: -webkit-linear-gradient(top, #efeaea, #dadada);
  background-image: -moz-linear-gradient(top, #efeaea, #dadada);
  background-image: -ms-linear-gradient(top, #efeaea, #dadada);
  background-image: -o-linear-gradient(top, #efeaea, #dadada);
  background-image: linear-gradient(top, #efeaea, #dadada);
}

#skillset .skill .bar .skillbar {
  display: block;
  height: 100%;
  text-align: center;
  font-weight: bold;
  font-size: 1.1em;
  color: #414141;
  text-shadow: 1px 1px 0 rgba(255,255,255,0.55);
  -webkit-border-radius: 4px;
  -moz-border-radius: 4px;
  border-radius: 4px;
  background-color: #4cc958;
  background-image: -webkit-gradient(linear, left top, left bottom, from(#68e574), to(#4cc958));
  background-image: -webkit-linear-gradient(top, #68e574, #4cc958);
  background-image: -moz-linear-gradient(top, #68e574, #4cc958);
  background-image: -ms-linear-gradient(top, #68e574, #4cc958);
  background-image: -o-linear-gradient(top, #68e574, #4cc958);
  background-image: linear-gradient(top, #68e574, #4cc958);
}
#skillset .skill .bar .skillbar small { font-size: 0.85em; padding-left: 3px; vertical-align: top; }

The outer .bar container uses a fixed width/height value to ensure everything looks the same in all browsers. The inner .skillbar does keep a height value with text, but in the second example, those span elements just wouldn’t appear because they’re empty. We need to use 100% height on all the internal skill bars to ensure they appear even without any content.

The centered text effect is also pretty cool because the internal content will animate along with the bar itself. You might not like this effect and it’s pretty simple to change the alignment. But if you consider how easy this all is, you might start brainstorming new concepts to try out.

Animation Fill Modes

When I first started animating the elements, each bar would finish the animation, then automatically resort back to 100% width. This obviously wasn’t my intention so I went in search of a solution.

I was reading that we can setup a default width on the original class like .htmlcss so it ends up at 100% width once the animation is done… or in the case of jQuery the class would need a width of 55%. I feel this almost defeats the purpose having to copy over the width value twice. Then I found out about fill modes which was something I had never heard about before.

#skillset .skill .bar .htmlcss {
  -moz-animation: htmlcss 1.5s ease-in-out forwards;
  -webkit-animation: htmlcss 1.5s ease-in-out forwards;
  animation: htmlcss 1.5s ease-in-out forwards;
}

#skillset .skill .bar .jquery {
  -moz-animation: jquery 1.5s ease-in-out forwards;
  -webkit-animation: jquery 1.5s ease-in-out forwards;
  animation: jquery 1.5s ease-in-out forwards;
}

#skillset .skill .bar .phpmysql {
  -moz-animation: phpmysql 1.5s ease-in-out forwards;
  -webkit-animation: phpmysql 1.5s ease-in-out forwards;
  animation: phpmysql 1.5s ease-in-out forwards;
}

#skillset .skill .bar .wordpress {
  -moz-animation: wordpress 1.5s ease-in-out forwards;
  -webkit-animation: wordpress 1.5s ease-in-out forwards;
  animation: wordpress 1.5s ease-in-out forwards;
}

#skillset .skill .bar .woocommerce {
  -moz-animation: woocommerce 1.5s ease-in-out forwards;
  -webkit-animation: woocommerce 1.5s ease-in-out forwards;
  animation: woocommerce 1.5s ease-in-out forwards;
}

Fill modes tell the animation how to utilize styles before & after completion. Using the keyword forwards will force each element to retain CSS styles found in the last keyframe. So in this case we are running a different animation for each class, and each animation has a 0% starting position followed by a 100% completed position. I never knew after the animation completes that it would revert back to the original styles.

If you want to read a bit more on fill modes check out this article by Mozilla which delves into greater detail. The concept is very simple, but I can understand how a new developer trying out CSS3 keyframe animations wouldn’t know anything about it.

Finalizing Keyframes

The last block of code I want to share are the keyframes themselves. We give each keyframe setup a unique name which is represented by the earlier animation properties. I’m using CSS3 prefixes for -moz and -webkit to handle compliance with as many browser versions as possible. You’ll notice the animations are split into blocks grouped by the animation name:

/** CSS3 keyframes **/
@-moz-keyframes htmlcss {
  0%   { width: 35px; }
  100% { width: 100%; }
}
@-webkit-keyframes htmlcss {
  0%   { width: 35px; }
  100% { width: 100%; }
}
@keyframes htmlcss {
  0%   { width: 35px; }
  100% { width: 100%; }
}


@-moz-keyframes phpmysql {
  0%   { width: 45px; }
  100% { width: 70%; }
}
@-webkit-keyframes phpmysql {
  0%   { width: 45px; }
  100% { width: 70%; }
}
@keyframes phpmysql {
  0%   { width: 45px; }
  100% { width: 70%; }
}


@-moz-keyframes jquery {
  0%   { width: 45px; } 
  100% { width: 55%; }
}
@-webkit-keyframes jquery {
  0%   { width: 45px; } 
  100% { width: 55%; }
}
@keyframes jquery {
  0%   { width: 45px; } 
  100% { width: 55%; }
}


@-moz-keyframes wordpress {
  0%   { width: 35px; } 
  100% { width: 65%; }
}
@-webkit-keyframes wordpress {
  0%   { width: 35px; } 
  100% { width: 65%; }
}
@keyframes wordpress {
  0%   { width: 35px; } 
  100% { width: 65%; }
}


@-moz-keyframes woocommerce {
  0%   { width: 35px; } 
  100% { width: 35%; }
}
@-webkit-keyframes woocommerce {
  0%   { width: 35px; } 
  100% { width: 35%; }
}
@keyframes woocommerce {
  0%   { width: 35px; } 
  100% { width: 35%; }
}

You’ll also notice each animation only uses a starting position and a completed position, all of which should animate within 1.5 seconds. Some of the starting positions use 35px or 45px to fully encapsulate the label text. You could change this value to 0px if you don’t want any text found within the skill bar itself.

Other than these styles, everything else should be pretty well-known. The last big section in my stylesheet redefines the inner gradients for each skill bar – without labels you can try incorporating colors to differentiate between skill sets. This is why my secondary set skill bars also include an .alt class.

Live DemoDownload Source Code

Closing

There are many other fantastic tutorials out there covering this topic and other keyframe animations. Many great code samples can be found on websites like CodePen and JSFiddle if you have time to search. Both my examples with inner text labels, or just the standard percentages, can work in any similar website design.

Be creative with these animations to see how they could fit into other future web projects. You can also download my tutorial source code and try building out more complex animations.