Web VFX

Spinners and charts with stroke-dasharray and stroke-dashoffset

A couple of years ago, I was tasked to develop some charts for an application, but we couldn’t add new libraries to the project. Initially, I was unsure about how to create them, but when I dove deeper into SVG’s, I was amazed with how powerful and easy to use they are for what I needed.

I managed to create the charts by using SVG properties, particularly stroke-dasharray and stroke-dashoffset.

Today, I’d like to show how you can use these properties to enhance your applications by creating a couple of elements and effects. So, let’s explore some practical examples that might inspire your own projects.

Stroke

Before getting into the main properties, I wanted to briefly mention what the stroke of an SVG is, in case you’ve never used it before.

Basically, the stroke of a path is the outline, similar to the border in CSS. The fill is the interior of the shape, it’s CSS equivalent would be the background.

You can define the stroke and the fill inline or using CSS:

<svg viewBox="0 0 20 10" xmlns="http://www.w3.org/2000/svg">
  <circle cx="5" cy="5" r="4" fill="black" stroke="green" />
</svg>

Stroke dasharray

The dasharray defines the pattern of dashes and gaps used to paint the border of the shape. We can pass list of comma and/or white space separated length’s and percentages.

<line stroke-dasharray="10 3" />

In this example, the first value of 10 is the lenght of the dash and the second value of 3, is the size of the gap, this will create the following dashed line:

Note that if you don’t specify the gap or use odd values, the list of values gets repeated to create an even list.

Stroke dashoffset

The stroke dash offset specifies where along the path the dash pattern will start. It could be confusing to understand since the term “offset” often implies pusing the content, but in the case of SVG’s it’s the opposite.

<line stroke-dasharray="10 3" stroke-dashoffset="5" />

Using the same example as before, we defined a line with a dasharray of 10 and a gap of 3, but this time we are adding a dashoffset of 5.

If you compare this example with the previous one, you can clearly notice how the first dash is much smaller now, and this is because we are using the offset to pull the rendering of our dashes by 5 units.

You can achieve the opposite result by using a negative offset, which will push the rendering forwards.

Loading spinner

Now that we understand how dasharray and dashoffset work, we can start using them to create some fun elements for our applications. The first one I’d like to introduce is a simple loading spinner.

We’ll start by defining a circle within our SVG container and adding a class to it in order to define the stroke, fill and width.

<svg viewBox="0 0 50 50">
  <circle class="path" cx="25" cy="25" r="20" />
</svg>
.path {
  stroke: black;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
}

Result:

Note that I’m using a large circle (20 radius) for displaying purposes. Loaders are normally much smaller

Right now, there’s nothing special with our circle, it definitely doesn’t look like a loading spinner so let’s add the stroke-dasharray to it now.

.path {
  ...
  stroke-dasharray: 1, 126;
}

Result:

We are using a value of 1 for our dash and a value of 126 for our gap. I know 126 seems like a very random number for our gap, but it’s not.

We want to animate a single dash in our circle and, since its radius is 20, the full circumference is around 125.6. This means that gap value of 126 guarantees that we only have one dash, even if it’s size is 1.

Now to the fun part, let’s animate the stroke-dasharray property to make the dash grow over time.

.path {
  ...
  animation: dash 1.5s ease-in-out infinite;
}

@keyframes dash {
  0% {
    stroke-dasharray: 1, 126;
  }
  50% {
    stroke-dasharray: 126, 126;
  }
  100% {
    stroke-dasharray: 126, 126;
  }
}

Result:

This is looking much better now, we’ve declared a new infinite keyframe animation that lasts 1.5 seconds and, by and it’s half, we make the dash lenght grow to fill the entire circle.

Why only animate it to fill the entire circle at the 50% of the animation? That’s because we’ll now animate the stroke-dashoffset to make it look like it’s shrinking the other half of the animation.

@keyframes dash {
  0% {
    stroke-dasharray: 1, 126;
    stroke-dashoffset: 0;
  }
  50% {
    stroke-dasharray: 126, 126;
    stroke-dashoffset: -50;
  }
  100% {
    stroke-dasharray: 126, 126;
    stroke-dashoffset: -124;
  }
}

Result:

With both the dasharray and the dashoffset being animated, we now have a pretty loading spinner in place.

Let’s add a bit more of life and randomness to it. To do so, we’ll also animate the entire SVG canvas to rotate infinitely.

<svg class="loader-container" viewBox="0 0 50 50"></svg>
.loader-container {
  animation: rotate 2s linear infinite;
}

@keyframes rotate {
  100% {
    transform: rotate(360deg);
  }
}

Result:

And that’s it! We’ve created an aweseom loading spinner with a simple SVG and animating its stroke-dasharray and stroke-dashoffset.

You are, of course, invited to play around with these values and come up with different animations. You can add color, or even use different shapes, there are no limits here.

You can find the full code for the spinner here.

Pie Chart

We can use a very similar approach in order to create pie or donut charts. Let’s start by adding a a circle element inside our SVG canvas.

<svg viewBox="0 0 50 50">
  <circle r="25" cx="25" cy="25"></circle>
</svg>

Result:

This is great, but how can we transform this circle into a slice of our pie chart? First, we need to get rid of the fill and define a stroke with the color we’d like to use. Then we’ll set a stroke-width matching the size of the diamater

<svg viewBox="0 0 50 50">
  <circle r="25" cx="25" cy="25" class="circle-1"></circle>
</svg>
.circle-1 {
  fill: none;
  stroke: #25a1cd;
  stroke-width: 50;
}

Result:

That didn’t turn out as we wanted, what happened? Well, since we are using such a large stroke-width it’s overflowing our SVG canvas, let’s fix that by increasing the viewBox and making sure our circle remains at the center.

<svg viewBox="0 0 100 100">
  <circle r="25" cx="50" cy="50" class="example-1-circle-1"></circle>
</svg>

Result:

Great! we are back with a circle now, we didn’t make it looke like a slice yet. For that we’ll finally need to add our stroke-dasharray. Again, we want a single dash.

.circle-1 {
  ...
  stroke-dasharray: 47 157;
}

Result:

Now it looks like a pie chart, we defined our stroke-dasharray with a value of 47 and 158. This is because we wanted a single dash so the gap must fill the entire circle circumference, which is around 157. Since we said the first slice was going to fill 30% of that, we defined it’s length to 47.

We can now add other slices using the same technique, let’s add one to fill 60% and the final one with 10%.

<div class="container">
  <svg viewBox="0 0 100 100">
    <circle r="25" cx="50" cy="50" class="slice circle-1"></circle>
    <circle r="25" cx="50" cy="50" class="slice circle-2"></circle>
    <circle r="25" cx="50" cy="50" class="slice circle-3"></circle>
  </svg>
</div>
.slice {
  fill: none;
  stroke-width: 50;
}

.circle-1 {
  stroke: #25a1cd;
  stroke-dasharray: 47 157;
}
.circle-2 {
  stroke: #e97556;
  stroke-dasharray: 94.2 157;
}
.circle-2 {
  stroke: #9377d6;
  stroke-dasharray: 15.7 157;
}

Result:

We have our 3 slices now but they are overlapping each other. To fix that we’ll want to use stroke-dashoffset to offset each circle to where the previous one ended, you’ll need to add previous offsets to the size of the last item to get the correct value.

Remember that we need to define a negative offset in order to push the rendering of the dash.

.circle-1 {
  stroke: #25a1cd;
  stroke-dasharray: 47 157;
}
.circle-2 {
  stroke: #e97556;
  stroke-dasharray: 94.2 157;
  stroke-dashoffset: -47;
}
.circle-3 {
  stroke: #9377d6;
  stroke-dasharray: 15.7 157;
  stroke-dashoffset: -141;
}

Result:

We’ve now created a pie chart with just a couple of SVG circles and using the stroke-dasharray and stroke-dashoffet properties to define the size of each slice and where it should begin.

You can use this technique to create simple charts without the need of any libraries.

The full code for the chart is available here.

Map Path

As a bonus example for this post, I wanted to show another application for the stroke-dasharray and stroke-dashoffset properties. That is defining a path and the progress of that path, pretty much how Maps applications show a route and where you are on that route.

For this we’ll first need to define our route using an SVG path

<svg width="306" height="150" viewBox="0 0 306 150">
  <path
    d="M30 124L1.55035 86.2306C1.23879 85.817 1.28068 85.2465 1.66494 84.8994C22.5647 66.0197 64.2509 28.2491 69 23.5C73.4581 19.0419 75.961 8.20038 76.829 2.27211C76.9235 1.62647 77.5867 1.22778 78.1944 1.46539C92.7616 7.16094 120.831 18.8311 125.5 23.5C130.271 28.2709 199.979 108.188 234.865 148.27C235.225 148.684 235.836 148.712 236.253 148.355L305 89.5"
  />
</svg>

Result:

I’m not going to go over the path definition and how it works now. I’ll write a more in-depth post talking about it, but for our purposes right now, we’ve just created a line resembling a map route.

Now, what we need to do is duplicate the path, because we’ll want to use one for the route and the other to show the progress within that route.

We’ll also make sure to add different stroke colors for both so we can easily identify them

Result:

As we’ve seen before, both lines are overlapping each other, what we want is to define a dasharray for the second one, marking the progress of the route. Once again, since we only need a single dash we make sure the gap is big enought to cover the entire path.

For complex paths it’s not as easy to calculate the length as it was for getting the circle’s circumference. Luckily, paths have a method called getTotalLength that we can use. In our case, the lenght is around 474.8 units. So we’ll want our gap to be 475 to be sure.

.route-progress {
  ...
  stroke-dasharray: 100, 475;
}

Result:

To finish this we can also animate the dasharray as the user moves along that route. Of course, we don’t have a user but as an example, I’ll add a simple animation to it so you can see it in action.

.example-3-progress {
  ...
  animation: progress 10s infinite;
}

@keyframes progress {
  0% {
    stroke-dasharray: 0, 475;
  }
  20% {
    stroke-dasharray: 24, 475;
  }
  40% {
    stroke-dasharray: 50, 475;
  }
  80% {
    stroke-dasharray: 110, 475;
  }
  100% {
    stroke-dasharray: 150, 475;
  }
}

Result:

Perfect! we are now animating the progress of a route by simply increasing the lenght of the dasharray.

As always, you can play around with these settings, change animations, paths, colors and come up with unique ways of animating a path.

The full code for this map route progress can be found here.

Conclusion

Today we learned what the stroke-dasharray and stroke-dashoffset are, how they work together and how to combine them with animations to create some elements.

The loading spinner, pie chart and the map route are just simple examples leveraging the power of these properties, but the possibilities are endless.

I hope you’ve learned something today and have fun!

#CSS #SVG #Beginner