javascriptsvghighchartscss-animationssvg-animate

Complex SVG progress bar animation


I have an svg shield compound from two path. I need to create progress bar like this: enter image description here

Shield should fills clockwise with green starting from the center at the top. Unfortunatly I don't have any expirience working with svgs. All that I found - was dash+offset trick that kinda could work with two correct strokes (I think two, because outer and inner part are not the same) and they should move simultaneously and also from corret position from the top.

Maybe someone works with highcharts and they are flexible enough for that task? Or should I ask a designer for correct path/parts of path? Any suggestions would help

Thank you

<svg xmlns="http://www.w3.org/2000/svg" id="b" viewBox="0 0 208 256">
  <g id="c">
    <g id="d">
      <path d="M104.008,230.733c34.201-19.594,53.976-31.944,65.831-43.87,10.089-10.149,14.161-19.658,14.161-37.101V53.467c-.462-.213-.987-.446-1.59-.704-14.122-5.86-59.818-22.552-75.371-28.132-2.049-.587-2.812-.63-3.102-.63-.137,0-.854.027-2.9.609-1.425.523-3.092,1.133-4.956,1.816-18.825,6.897-57.753,21.16-70.689,26.42-.522.217-.983.416-1.392.598v96.319c0,9.414,1.253,16.008,3.303,21.237,2.011,5.129,5.262,10.15,10.888,15.797,11.888,11.934,31.67,24.279,65.818,43.937ZM95.55,253.553C24.65,212.861,0,195.992,0,149.762V52.668c0-13.264,5.718-17.642,16.264-22.02,13.307-5.418,52.881-19.916,71.606-26.775,2.194-.804,4.101-1.503,5.647-2.07,3.431-1.03,6.862-1.803,10.42-1.803,3.684,0,7.115.773,10.546,1.803,14.739,5.279,62.387,22.663,77.253,28.845,10.546,4.507,16.264,8.756,16.264,22.02v97.094c0,46.23-24.523,63.228-95.55,103.791-3.304,1.674-6.48,2.447-8.513,2.447s-5.083-.773-8.387-2.447Z" 
            style="fill:#0a0b0c; fill-rule:evenodd; stroke-width:0px;">
      </path>
    </g>
  </g>
</svg>


Solution

  • What makes it tricky is that your path has a fill, not a stroke. If your shape was a circle, a straight line or whatever with a stroke it would be easier just you use stroke-dasharray and pathLength directly on that.

    In the following example I use your path as a mask on another path. This path has a stroke and it follows the same shape as you path.

    The gray "background stroke" is made of a rectangle -- also using your path as a mask.

    let p1 = document.getElementById('p1');
    let t1 = document.getElementById('t1');
    document.forms.form01.range.addEventListener('input', e => {
      let value = e.target.valueAsNumber;
      p1.style.strokeDasharray = `${value} 100`;
      t1.textContent = `${value}%`;
    });
    body {
      display: flex;
      flex-direction: row;
    }
    
    svg {
      height: 95vh;
    }
    <form name="form01">
      <input name="range" type="range" value="50" min="0" max="100" />
    </form>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 208 256">
      <defs>
        <mask id="m1">
          <path d="M104.008,230.733c34.201-19.594,53.976-31.944,65.831-43.87,10.089-10.149,14.161-19.658,14.161-37.101V53.467c-.462-.213-.987-.446-1.59-.704-14.122-5.86-59.818-22.552-75.371-28.132-2.049-.587-2.812-.63-3.102-.63-.137,0-.854.027-2.9.609-1.425.523-3.092,1.133-4.956,1.816-18.825,6.897-57.753,21.16-70.689,26.42-.522.217-.983.416-1.392.598v96.319c0,9.414,1.253,16.008,3.303,21.237,2.011,5.129,5.262,10.15,10.888,15.797,11.888,11.934,31.67,24.279,65.818,43.937ZM95.55,253.553C24.65,212.861,0,195.992,0,149.762V52.668c0-13.264,5.718-17.642,16.264-22.02,13.307-5.418,52.881-19.916,71.606-26.775,2.194-.804,4.101-1.503,5.647-2.07,3.431-1.03,6.862-1.803,10.42-1.803,3.684,0,7.115.773,10.546,1.803,14.739,5.279,62.387,22.663,77.253,28.845,10.546,4.507,16.264,8.756,16.264,22.02v97.094c0,46.23-24.523,63.228-95.55,103.791-3.304,1.674-6.48,2.447-8.513,2.447s-5.083-.773-8.387-2.447Z" fill-rule="evenodd" fill="white" />
        </mask>
      </defs>
      <rect width="208" height="256" fill="LightGray"
        mask="url(#m1)" />
      <path id="p1" d="M 104 12 Q 108 12 113 14 L 191 45 Q 197 47 197 51 L 196 149 C 196 170 192 184 180 196 L 116 240 Q 103 248 90 239 L 32 200 C 17 187 11 168 11 150 L 12 53 Q 12 47 17 45 L 96 14 Q 100 12 104 12"
        stroke="MediumSeaGreen" stroke-width="30" fill="none"
        mask="url(#m1)" pathLength="100" stroke-dasharray="50 100"/>
       <text id="t1" x="50%" y="50%" font-size="35" font-weight="bold"
         font-family="sans-serif" dominant-baseline="middle"
         text-anchor="middle">50%</text>
    </svg>

    The "simple" solution would be to redraw the shape as a single path:

    let u1 = document.getElementById('u1');
    let t1 = document.getElementById('t1');
    document.forms.form01.range.addEventListener('input', e => {
      let value = e.target.valueAsNumber;
      t1.textContent = `${value}%`;
      if(value > 99) value = 101;
      u1.style.strokeDasharray = `${value} 100`;
    });
    body {
      display: flex;
      flex-direction: row;
    }
    
    svg {
      height: 95vh;
    }
    <form name="form01">
      <input name="range" type="range" value="50" min="0" max="100" />
    </form>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 104 133">
      <defs>
        <path id="p1" d="M 0 0 Q 2 0 5 1 L 42 14 Q 45 15 45 18
        V 75 Q 45 85 40 89 L 5 117 Q 0 121 -5 117 L -40 89
        Q -45 85 -45 75 V 18 Q -45 15 -42 14 L -5 1 Q -2 0 0 0 Z"
        stroke-width="14" fill="none" pathLength="100"/>
      </defs>
      <g transform="translate(52 7)">
        <use href="#p1" stroke="LightGray"/>
        <use id="u1" href="#p1" stroke="MediumSeaGreen" 
          stroke-dasharray="50 100" />
        <text id="t1" y="50" font-size="20" font-weight="bold"
          font-family="sans-serif" dominant-baseline="middle"
          text-anchor="middle">50%</text>
     </g>
     
    </svg>