A radial progress meter can be a unique twist on the traditional progress bar. Here we'll implement a visually appealing "ring" style radial bar using SVG and CSS. This solution is desirable not only because it is really simple to implement and understand, it also has good cross-browser and display size support.

The Ring

The first step is creating the base ring that will comprise the background of our progress meter. We'll use a SVG <circle> element with a fill set to 'none' and a stroke-width that's 1/10th the total outside circumference of our circle.

The stroke-width is calculated outwards in both directions from the radius of the circle, so for a circle with a total width of 120 and stroke-width of 12 we can calculate the radius as:

  // these units are relative to the viewport.
(120 / 2) - (12 / 2) = 54

This gives us a nice looking ring centered in the SVG's viewport:

  <svg width="120" height="120" viewBox="0 0 120 120">
  <circle cx="60" cy="60" r="54" fill="none" stroke="#e6e6e6" stroke-width="12" />
</svg>

Making Progress

Now that we have a background for our meter, we need to somehow show progress on it. We can easily achieve this by layering another duplicate ring over the ring we just created. This ring will be colored differently than the background, orange in this case.

  <svg width="120" height="120" viewBox="0 0 120 120">
    <circle cx="60" cy="60" r="54" fill="none" stroke="#e6e6e6" stroke-width="12" />
    <circle cx="60" cy="60" r="54" fill="none" stroke="#f77a52" stroke-width="12" />
</svg>

Different Strokes For Different Folks

Now a progress meter that always displays 100% progress is optimistic, but ultimately useless to us in its current form. We need some way of showing only part of the circle's stroke to indicate a range of progressing values. Enter stroke-dasharray and stroke-dashoffset, our super-secret special weapons.

There are many really cool use cases for these two SVG attributes. One such application is in "drawing" lines with the stroke of SVGs. Two really great articles for an introduction to this concept are Jake Archibald's Animated Line Drawing In SVG and Chris Coyier's How SVG Line Animation Works.

The basic concept is as follows. We create a "dash" that covers the entirety of the path we would like to draw. In this case, we're drawing on the circumference of the circle. With some rudimentary math, we know the circumference of our circle is:

  2 * π * R  = C
2 * π * 54 ≈ 339.292

We then set this value into both the stroke-dasharray and stroke-dashoffset attributes of the second circle in the SVG.

  <svg width="120" height="120" viewBox="0 0 120 120">
    <circle cx="60" cy="60" r="54" fill="none" stroke="#e6e6e6" stroke-width="12" />
    <circle cx="60" cy="60" r="54" fill="none" stroke="#f77a52" stroke-width="12"
        stroke-dasharray="339.292" stroke-dashoffset="339.292" />
</svg>

Wait, we've taken a step back haven't we? Instead of showing 100% progress, we're now showing 0% progress again. What's happening here?

The stroke-dasharray attribute we set can also be written another way:

  stroke-dasharray="339.292 339.292"

This syntax functionally equivalent to what we had before, but it helps to emphasize what's going on. The first value is the dash length and the second value is the length of spaces between dashes. The dashes repeat cyclically between the beginning and end of the path. We set a dash representative of the progress we want to eventually show, but we then pushed it out with an offset of equal length. What remains in the circle now is the gap between the dash, which is invisible and also equal to the circle's circumference.

All we need to do now is subtract from the offset in order to make the dash, and thus progress, display. The length of the offset is inversely proportional to the amount of progress. For example, if we want to display 60% progress, we set the offset as 40% of the circumference.

  339.292 * (1 - 0.6) ≈ 135.717

Final Touches

We're so close! Our progress meter is displaying 60% progress, but it seems to be offset by 90°. No problem, we can easily fix that by adding a rotation transform to the SVG.

  transform: rotate(-90deg);

And that's it, we're done!

We can control the offset in both CSS and JavaScript. Also, a round stroke-linecap can give the value our progress meter a rounded appearance.

Now go out there and make some progress!