I would like to fill a custom SVG to a specific percentage.
Here is my initial SVG
<svg width="202" height="195" viewBox="0 0 202 195" fill="none" xmlns="http://www.w3.org/2000/svg"> <path opacity="0.1" d="M96.8166 4.06964C16.0794 8.40606 -20.4645 94.8546 20.2957 157.019C54.6867 204.16 143.361 202.123 184.273 150.807C226.464 97.5789 163.505 0.38025 96.8166 4.06964Z" stroke="#313848" stroke-width="6.87634" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/> </svg>
Suppose there is a progress of x%
so I would like to fill this SVG like
<svg width="207" height="203" viewBox="0 0 207 203" fill="none" xmlns="http://www.w3.org/2000/svg"> <path opacity="0.1" d="M99.8166 12.0696C19.0794 16.4061 -17.4645 102.855 23.2957 165.019C57.6867 212.16 146.361 210.123 187.273 158.807C229.464 105.579 166.505 8.38025 99.8166 12.0696Z" stroke="#313848" stroke-width="6.87634" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/> <path d="M99.8142 12.0736C166.502 8.38527 229.463 105.585 187.273 158.812" stroke="#EA7052" stroke-width="6.87634" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/> <path d="M96.1683 2.4287C88.1789 2.85671 84.5529 11.2658 88.579 17.3074C91.9765 21.8887 100.751 21.6836 104.805 16.6905C108.986 11.5113 102.768 2.06471 96.1683 2.4287Z" fill="#EDEDEE" stroke="#EA7052" stroke-width="4.76054" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/> <path d="M171.545 162.236C169.583 169.548 177.007 175.33 184.329 173.522C189.985 171.84 192.408 163.889 188.57 158.747C184.582 153.434 173.156 156.193 171.545 162.236Z" fill="#EDEDEE" stroke="#EA7052" stroke-width="4.76054" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/> </svg>
I am not able to figure out how to achieve this. I want it to be dynamic so that I can make it for any percentage.
Also, I need to animate it from the starting point to the endpoint in a circular motion.
Any help would be highly appreciatable.
Advertisement
Answer
As I’ve commented:
You can calculate the length of the path using the getTotalLength()
method. This represents 100%.
Next you can get the length representing the x% (xperc
in the code).
Now you can use stroke-dasharray
to represent the partial path.
You can calculate the position of the last point using the getPointAtLength()
method.
Please read the comments in my code.
//the desired percentege let xperc = .35; //the total length of the path let tl = base.getTotalLength(); //the partial length at the given percentage xperc let partial = tl * xperc; //set the stroke-dasharray of the second use element perc.setAttribute("stroke-dasharray", `${partial} ${tl -partial}`) //calculate the position of the point marking the end position let theEnd = base.getPointAtLength(partial); // set the cx and the cy attributes for the end point end.setAttribute("cx", theEnd.x); end.setAttribute("cy", theEnd.y);
circle { stroke: red; fill:white; stroke-width: 6.87634; }
<svg width="207" height="203" viewBox="0 0 207 203" fill="none" xmlns="http://www.w3.org/2000/svg"> <defs> <path id="base" d="M99.8166,12.0696L99.8166,12.0696C166.505,8.38025 229.464,105.579 187.273,158.807C146.361,210.123 57.6867,212.16 23.2957,165.019C-17.4645,102.855 19.0794,16.4061 99.8166,12.0696Z" stroke-width="6.87634" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/> </defs> <use xlink:href="#base" stroke="silver" /> <use xlink:href="#base" stroke="red" id="perc" /> <circle cx="99.8166" cy="12.0696" r="10" /> <circle id="end" r="10" /> </svg>
OBSERVATION: since your path goes counter clockwise I had to reverse the path to get the desired result
And this is an example where I’m using an input type range to change the percent value:
let xperc = itr.value; onInput(); itr.addEventListener("input", onInput) function onInput() { xperc = itr.value; let tl = base.getTotalLength(); let partial = tl * xperc; perc.setAttribute("stroke-dasharray", `${partial} ${tl - partial}`); let theEnd = base.getPointAtLength(partial); end.setAttribute("cx", theEnd.x); end.setAttribute("cy", theEnd.y); }
circle { stroke: red; fill:white; stroke-width: 6.87634; }
<input id="itr" type="range" min="0" max="1" step=".001" value=".35" /><br> <svg width="207" viewBox="-5 -5 220 220" fill="none" xmlns="http://www.w3.org/2000/svg"> <defs> <path id="base" d="M99.8166,12.0696L99.8166,12.0696C166.505,8.38025 229.464,105.579 187.273,158.807C146.361,210.123 57.6867,212.16 23.2957,165.019C-17.4645,102.855 19.0794,16.4061 99.8166,12.0696Z" stroke-width="6.87634" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/> </defs> <use xlink:href="#base" stroke="silver" /> <use xlink:href="#base" stroke="red" id="perc" /> <circle cx="99.8166" cy="12.0696" r="10" /> <circle id="end" r="10" /> </svg>
And another demo where I’m using javascript to animate it from 0 to 1:
//the animation begins at 0 let xperc = 0; //get the total length of the path let tl = base.getTotalLength(); //the request animation id let rid = null; function Animation() { rid = window.requestAnimationFrame(Animation); // while xperc < 1 increase it's value by 0.001. Else stop the animation if (xperc < 1) { xperc += 0.001; }else{window.cancelAnimationFrame(rid)} //the same as in the first example let partial = tl * xperc; perc.setAttribute("stroke-dasharray", `${partial} ${tl - partial}`); let theEnd = base.getPointAtLength(partial); end.setAttribute("cx", theEnd.x); end.setAttribute("cy", theEnd.y); } Animation();
circle { stroke: red; fill:white; stroke-width: 6.87634; }
<svg width="207" viewBox="-5 -5 220 220" fill="none" xmlns="http://www.w3.org/2000/svg"> <defs> <path id="base" d="M99.8166,12.0696L99.8166,12.0696C166.505,8.38025 229.464,105.579 187.273,158.807C146.361,210.123 57.6867,212.16 23.2957,165.019C-17.4645,102.855 19.0794,16.4061 99.8166,12.0696Z" stroke-width="6.87634" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/> </defs> <use xlink:href="#base" stroke="silver" /> <use xlink:href="#base" stroke="red" id="perc" /> <circle cx="99.8166" cy="12.0696" r="10" /> <circle id="end" r="10" /> </svg>