How To Create A Circular Progress Bar using HTML CSS & Simple JavaScript

0 Comments

Circular progress bars are a staple of web development. It’s one of those little minor details that can immensely impact a user’s experience and flow. While it’s easy to cheat and just get one of those looped animated loading icon gifs, or spin an element on the page — a really cool effect to have is a proper loading bar with a percentage progression.

But how exactly do you do that?

Turns out you only need a dash of HTML, a little bit of CSS, and a pinch of a JavaScript function. This is the write-up part for the video below. Don’t forget to like and subscribe 😉

Tutorial Version:

Speed Coding Version:


This is what we will be making:

note: The color inside the bar is actually solid purple but my screen to gif capture isn’t doing it properly. In short, this is the general gist of what it looks like.

Features:

  • circular loading bar
  • number in the middle also counts up to show the percentage
  • the loading bar loads at the same time as the percentage

If we dissect the image above, there are the following bits and pieces:

  • an outer circle
  • an ‘inner’ circle to create the rim effect
  • a progress bar
  • a progress percentage number

Let’s put all of this in the HTML:

<div class="circular">
  <div class="inner"></div>
  <div class="number">100%</div>
  <div class="circle">
     <div class="bar left">
        <div class="progress"></div>
     </div>
     <div class="bar right">
        <div class="progress"></div>
     </div>
   </div>
</div>

In the HTML above, we have a container class called circular. This will hold everything together and help ring-fence your CSS from other parts of your HTML. It also acts as the outer circle.

The second class is the inner circle. The number will sit inside here.

The circle class is the container for the progress bars. The way the progress bar works is that we are going to create clipped rectangles and rotate them to fill the space between our two circles.

Here’s a gif recording of what I’m trying to explain:

Now that we’ve got the basic HTML sorted, let’s move onto the CSS part.


This next part of the CSS isn’t compulsory, but it’s nice to have if you’re coding this from scratch. This just centers and sets the font family for your loader.

*{
  margin: 0;
  padding: 0;
  font-family: 'Arial', sans-serif;
}
html, body{
  display:grid;
  height:100%;
  text-align: center;
  place-items: center;
  background: #dde6f0;
}

Let’s start our CSS off by setting the height and width of container class .circular . It also needs to be relatively positioned because we want to use it as a reference point where all other absolutely positioned elements are going to go. I’ve put an extra green box around .circular so you can see where the boundary line is.

.circular{
  height:100px;
  width: 100px;
  position: relative;  
  border:solid green 1px;
}

Now it’s time to deal with the ‘inner circle’.

.circular .inner{
  position: absolute;
  z-index: 6;
  top: 50%;
  left: 50%;
  height: 80px;
  width: 80px;
  margin: -40px 0 0 -40px;
  background: red;
  border-radius: 100%;
}

Here’s what it looks like:

Now, let's move the percentage number into the right position and do a little bit of styling. This is done through position: absolute where the coordinates supplied are based on the relative we set on the parent selector circular.

.circular .number{
  position: absolute;
  top:50%;
  left:50%;
  transform: translate(-50%, -50%);
  z-index:10;
  font-size:18px;
  font-weight:500;
  color:#4158d0;
}

Here’s what it looks like now:

Now it’s time to start styling the progress bar.

.circular .bar{
  position: absolute;
  height: 100%;
  width: 100%;
  background: #fff;
  -webkit-border-radius: 100%;
  clip: rect(0px, 100px, 100px, 50px);
}

What’s happening here is we are creating another box with the same width and height as the circular container and then turning it into a circle. However, we are only doing one side first so that when we rotate our animation, it doesn’t impact the other half.

Here is what it looks like now:

Now, we are going to create the actual loader part. First, we are going to create the loader part. This is called progress in our HTML. What’s happening below is that we’ve created a circle that fills the space available. We’ve clipped it into a half circle so the animation only shows on one side a time.

.circle .bar .progress{
  position: absolute;
  height: 100%;
  width: 100%;
  -webkit-border-radius: 100%;
  clip: rect(0px, 50px, 100px, 0px);
  background: #4158d0;
}

Right now, it doesn’t look like much. Let’s animate it. The @keyframes function lets us create custom animations and link it to our animation property inside the left and right .progress properties.

.circle .left .progress{
  z-index:1;
  animation: left 4s linear both;
}
@keyframes left{
  100%{
    transform: rotate(180deg);
  }
}
.circle .right {
  transform: rotate(180deg);
  z-index:3;
 
}
.circle .right .progress{
  animation: right 4s linear both;
  animation-delay:4s;
}
@keyframes right{
  100%{
    transform: rotate(180deg);
  }
}

Here’s what it looks like now:

And that’s basically it. Now it’s just a matter of cleaning up the colors and getting rid of the green border. For mine, I’ve changed background:red at .circular .inner to match the color of the page’s background.

Here’s what it looks like now:

This last section deals with the JavaScript that lets you run the number up as the progress bar moves around.

There are multiple ways of achieving this — but for this tutorial, we’re just going to go over the bare basics. The point is so that you can use this information to create your own implementations.

First, we are going to target the .number class, where your percentage number is currently located. We also need a counter variable to keep track of your percentage value.

One way to achieve the counting up effect is to set an interval as per the code below.

const numb = document.querySelector(".number");
let counter = 0;
setInterval(() => {
  if(counter == 100 ){
    clearInterval();
  }else{
    counter+=1;
    numb.textContent = counter + "%";
  }
}, 80);

You can adapt this little piece of code to increase the percentage and the loader bar as your app loads. To do this, you remove the animation from your CSS and manually create the animation by increasing the transform: rotate(180deg); . So instead of starting off with 180deg and then using @keyframes to animate it, you are adding a degree or two to incrementally step up your progress bar as the thing you’re loading loads.

And that’s basically it.

Here is all the code used for this tutorial.

The HTML:

<div class="circular">
    <div class="inner"></div>
    <div class="number">100%</div>
    <div class="circle">
      <div class="bar left">
        <div class="progress"></div>
      </div>
      <div class="bar right">
        <div class="progress"></div>
      </div>
    </div>
  </div>

The CSS:

*{
  margin: 0;
  padding: 0;
  font-family: 'Arial', sans-serif;
}
html, body{
  display:grid;
  height:100%;
  text-align: center;
  place-items: center;
  background: #dde6f0;
}
.circular{
  height:100px;
  width: 100px;
  position: relative;
  transform:scale(2);
}
.circular .inner{
  position: absolute;
  z-index: 6;
  top: 50%;
  left: 50%;
  height: 80px;
  width: 80px;
  margin: -40px 0 0 -40px;
  background: #dde6f0;
  border-radius: 100%;
 
}
.circular .number{
  position: absolute;
  top:50%;
  left:50%;
  transform: translate(-50%, -50%);
  z-index:10;
  font-size:18px;
  font-weight:500;
  color:#4158d0;
}
.circular .bar{
  position: absolute;
  height: 100%;
  width: 100%;
  background: #fff;
  -webkit-border-radius: 100%;
  clip: rect(0px, 100px, 100px, 50px);
}
.circle .bar .progress{
  position: absolute;
  height: 100%;
  width: 100%;
  -webkit-border-radius: 100%;
  clip: rect(0px, 50px, 100px, 0px);
  background: #4158d0;
}
.circle .left .progress{
  z-index:1;
  animation: left 4s linear both;
}
@keyframes left{
  100%{
    transform: rotate(180deg);
  }
}
.circle .right {
  transform: rotate(180deg);
  z-index:3;
 
}
.circle .right .progress{
  animation: right 4s linear both;
  animation-delay:4s;
}
@keyframes right{
  100%{
    transform: rotate(180deg);
  }
}

The JavaScript:

const numb = document.querySelector(".number");
let counter = 0;
setInterval(() => {
  if(counter == 100 ){
    clearInterval();
  }else{
    counter+=1;
    numb.textContent = counter + "%";
  }
}, 80);