Simple svg css progress circle
Asked Answered
I

3

8

I am trying to look for a way to achieve a simple progress circle (static) with no animations. The examples I have found have very different offsets for percentage such as given in the example below. How do I make my progress circle in such a way that if I provide offset as 50%, then it is exactly 50% (half filled)?

.u-absoluteCenter {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  margin: auto;
}
.u-flexCenter {
  display: flex;
  align-items: center;
  justify-content: center;
}
.u-offscreen {
  position: absolute;
  left: -999em;
}
.demo {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  margin: auto;
  display: flex;
  align-items: center;
  justify-content: center;
}
.progress {
  transform: rotate(-90deg);
}
.progress__value {
  stroke-dasharray: 0;
  stroke-dashoffset: 0;
}
@-webkit-keyframes progress {
  from {
    stroke-dashoffset: 339.292;
  }
  to {
    stroke-dashoffset: 0;
  }
}
@keyframes progress {
  from {
    stroke-dashoffset: 339.292;
  }
  to {
    stroke-dashoffset: 0;
  }
}
<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>
Isocline answered 7/4, 2021 at 16:44 Comment(1)
For 25% try stroke-dasharray="84.823 254.469" where the stroke 84.823 represents 25% of the path's length (339.292) and the gap (254.469) represents the 75%.Hotfoot
W
21

You can leverage an SVG attribute to set the path length rather than having to calculate it.

pathLength sets the length to whatever you need...say 100 for a progress bar.

The pathLength attribute lets authors specify a total length for the path, in user units. This value is then used to calibrate the browser's distance calculations with those of the author, by scaling all distance computations using the ratio pathLength/(computed value of path length).

pathLength="100"

Then you can set the stroke-dasharray to 100 as well and then adjust the stroke-dashoffset as needed....

::root {
  --val: 0;
}

svg {
  transform: rotate(-90deg);
}

.percent {
  stroke-dasharray: 100;
  stroke-dashoffset: calc(100 - var(--val));
}

.fifty {
  --val: 50;
}

.sixty {
  --val: 60;
}

.ninety {
  --val: 90;
}
<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 class="percent fifty" cx="60" cy="60" r="54" fill="none" stroke="#f77a52" stroke-width="12" pathLength="100" />
</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 class="percent sixty" cx="60" cy="60" r="54" fill="none" stroke="#f77a52" stroke-width="12" pathLength="100" />
</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 class="percent ninety" cx="60" cy="60" r="54" fill="none" stroke="#f77a52" stroke-width="12" pathLength="100" />
</svg>
Wodge answered 7/4, 2021 at 17:16 Comment(5)
But this only gives 3 values (50, 60, 90) and I think OP would need any value between 0 - 100. Does this apply?Tugboat
The values are just for demonstration purposes. Adjusting the value is simple enoughWodge
Ah okay. Could you explain how can one provide a value from the HTML code? like 26%Tugboat
Like this @Tugboat codepen.io/andywebo/pen/gORyOYQ ...just do the calc in the style attribute inline.Strangles
Just noticed that the calc(100 - x) value doesn't work within stroke-dashoffset on mobile Safari nor mobile Chrome (on iOS). Have to use the resultant number instead.Strangles
G
5

As Paulie says, pathLength is the key to progress circles

A modern JavaScript Web Component (JSWC supported in all modern browsers) makes for a re-usable HTML Element

 <svg-progress-circle percent="30"></svg-progress-circle>
 <svg-progress-circle percent="20" color="blue"></svg-progress-circle>
 <svg-progress-circle percent="80" color="gold"></svg-progress-circle>

Added a range-input for interactive demo purposes.

Percent is a property on the element, you can set with code like:

    document.getElementById("Slider1").percent = <PERCENTAGE>;

If you don't want a dashed grey fullcircle, delete the dash setting from the pathLenght=120 path

I used a path instead of overlapping circles because with some other settings the almost same code can create pie-charts.

<style>
  svg { width: 150px; background: teal }
  svg-progress-circle[percent="100"] path { stroke: green }
</style>

<svg-progress-circle percent="30"></svg-progress-circle>
<svg-progress-circle percent="20" color="blue"></svg-progress-circle>
<svg-progress-circle percent="80" color="gold"></svg-progress-circle>

<script>
  customElements.define("svg-progress-circle", class extends HTMLElement {
    connectedCallback() {
      let d = 'M5,30a25,25,0,1,1,50,0a25,25,0,1,1,-50,0'; // circle
      this.innerHTML = 
      `<input type="range" min="0" max="100" step="10" value="30"`+ // delete 2 lines
      ` oninput="this.parentNode.percent=this.value" /><br>`+ // just for demo
      
      `<svg viewBox="0 0 60 60">
       <path stroke-dasharray="10 2"   stroke-dashoffset="-19" 
             pathlength="120" d="${d}" fill="grey" stroke="lightgrey" stroke-width="5"/>
       <path stroke-dasharray="30 70" stroke-dashoffset="-25" 
             pathlength="100" d="${d}" fill="none" 
             stroke="${this.getAttribute("color")||"red"}" stroke-width="5"/>
       <text x="50%" y="57%" text-anchor="middle">30%</text></svg>`;
       
      this.style.display='inline-block';
      this.percent = this.getAttribute("percent");
    }
    set percent(val = 0) {
      this.setAttribute("percent", val);
      let dash = val + " " + (100 - val);
      this.querySelector("path+path").setAttribute('stroke-dasharray', dash);
      this.querySelector("text").innerHTML = val + "%";
      this.querySelector("input").value = val;
    }
  })
</script>

Note: I am working on a complete Web Component that does Pie Graphs and fancy Progress circles like:

But, it is one of many side-projects... HTML examples and obfuscated source code available at https://pie-meister.github.io/

Girasol answered 7/4, 2021 at 18:17 Comment(0)
U
0

I managed to create this simple static progress bar by using the the different attributes provided for svg elements:

Here is an example for a progress bar with 80%

viewBox="0 0 100 100" 100 * 100 is the box size, for easy calculations.

cx="50" cy="50" x=50, y=50 is the center point of the circle.

r="45" radius is 45 because the stroke-width is 10px, this way the edge of the stroke is just at the edge of the box. The stroke is 5px on each side of where the radius ends.

pathLength allows to define the total length of the path in user units. So 100 in this case, since we want the full circle to be 100%.

stroke-dasharray defines a pattern of dashes and gaps for the path. We use the value 80 20 here. 80% for the filled up portion and 20% for the empty portion. This way once we apply the offset the path stays in the correct shape.

stroke-dashoffset defines where to start the stroke. It starts at the right side by default, so defining it at -75 pushes the starting point to the top.

<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <circle
    cx="50"
    cy="50"
    r="45"
    fill="transparent"
    stroke="#e0e0e0"
    stroke-width="10px"
  />
    <circle
    cx="50"
    cy="50"
    r="45"
    fill="transparent"
    stroke="#60e6a8"
    stroke-width="10px"
    pathLength="100"
    stroke-dasharray="80 20"
    stroke-dashoffset="-75"
  />
  <text x="50%" y="50%" text-anchor="middle" alignment-baseline="middle">80%</text>
</svg>
Unloosen answered 20/9, 2024 at 7:43 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.