Animated SVG Radial Progress Bars

Using a single path SVG, a smidge of CSS, and ~6 lines of JavaScript

March 29, 2018

For a client project we tasked ourselves with building out one of those cool radial progress bars. In the past, we’ve used entire Canvas-based based charting libraries (156k/44k gzip), but that seemed like overkill. I looked at Airbnb’s Lottie project where you export After Effects animations as JSON. This is cool for complex animations, but the dependencies seemed heavy (248k/56k gzip) for one micro-animation.

Per the usual, I tried my hand at a minimal custom SVG with CSS animation and a small bit of JavaScript (~223b gzip). I’m pleased with the results.

See the Pen Animated SVG Not-so Radial Progress Bar by Dave Rupert (@davatron5000) on CodePen.

It works in IE9+, Edge, Chrome, Firefox, and Safari. The animation (actually, a transition) is a progressive enhancement, so browsers that don’t support CSS transitions in SVG will get a static graph.

If you’d like to see how I made this, keep reading…

How it works: Clone, Measure, and Transition

I’ll give a high-level overview of how it works. Behind the scenes we’re using the same animated line drawing technique that powered Polygon’s epic Xbox One Review animation. I like it because it appears complex but is actually a single transition to a single stroke-dashoffset property on a single <path>.

But before we get all complex and animate an arc, let’s start simple by animating a non-curved progress meter to see how it all works…

See the Pen Animated SVG Not-so Radial Progress Bar by Dave Rupert (@davatron5000) on CodePen.

It’s worth clicking through the HTML/CSS/JS panels on the CodePen. You can probably read it in 1 minute. To get here though we needed to do a bit of work. The HTML/CSS workflow looks like this:

  1. Create an SVG with a single <path>. This will be the “empty” background.
  2. Run the SVG through SVGOMG.
  3. Copy & Paste the path and give the clone a class="meter".
  4. Change the stroke color of the meter.
  5. Apply a transition: stroke-dashoffset 850ms to the meter.
  6. Hardcode the stroke-dasharray and stroke-dashoffset to the length of the meter.

You can dynamically set the stroke-dash* properties, but to prevent the chance of an animation glitch, it’s worth hardcoding ahead of time. To get the length of the path, type this into the console:

document.querySelector('path').getTotalLength();

The next thing I’m doing is specifying the meter’s value by using a data-value attribute on the SVG. Your final SVG should look something like the following:

<svg width="200" height="20" data-value="40">
  <style>
  .bg, .meter {
    fill: none;
    stroke-width: 20px;
    stroke-miterlimit: round;
  }
  .meter {
    transition: stroke-dashoffset 850ms ease-in-out;
    stroke-dasharray: 200; 
    stroke-dashoffset: 200;
  }
  </style>
  <path class="bg" stroke="#ccc" d="M0 10, 200 10"></path>
  <path class="meter" stroke="#09c" d="M0 10, 200 10">
  </path>
</svg>

SVGOMG might convert some styles to properties. Either way is fine. Then on the JavaScript side it’s a 4-step process:

  1. Measure the length of the path
  2. Get the passed-in value from data-value
  3. Calculate the new offset (percentage of the original)
  4. Set the new stroke-dashoffset
document.querySelectorAll('path.meter').forEach(path => {
  let length = path.getTotalLength();
  let maxValue = 100;
  let value = parseInt(path.parentNode.getAttribute('data-value'));
  let to = length * ((maxValue - value) / 100);

  // Trigger Layout in Safari Hack 
  // https://jakearchibald.com/2013/animated-line-drawing-svg/
  path.getBoundingClientRect();
  path.style.strokeDashoffset = Math.max(0, to);  
});

Now our meters should be filling up to the correct amount on page load. For older browsers you’ll need the 200b NodeList forEach polyfill.

One improvement you could make is dynamically setting maxValue in a data-attribute like value. Another improvement would be a touch of accessibility, maybe some descriptive text in a <title> element.

Radical Radials

Now that we know how to animate a straight path, let’s make a radial path and do the same clone, measure, and transition exercise.

See the Pen Animated SVG Not-so Radial Progress Bar by Dave Rupert (@davatron5000) on CodePen.

The effect is so basic, you can animate any single path you want.

See the Pen Animated SVG Not-so Radial Progress Bar by Dave Rupert (@davatron5000) on CodePen.

I like how this turned out. Saving a couple orders of magnitude in your codebase is always nice. Whenever I need something fancier than a <progress> element, I’ll reach for this.

If you like these “6 Lines of JavaScript” posts, be sure to Like, Share, and Subscribe in your RSS reader of choice.