I recently worked on creating an SEO analyzer for Hyvor Blogs, which required a score meter to display the SEO score. This is a short guide on how I designed it. The final result will look like this:
We’ll be using React, but you can easily use the same concept with any other framework. The ‘react part’ is quite trivial.
Full Code on Codepen
Parts of the Score Meter
These are the parts of the score meter. We’ll be using SVG for the progress bar.
💡 As someone who is not too familiar with SVG, my first thought was to design the full component using just CSS. I tried several different ways using circles, etc. However, I could not get the perfect round edges. So, here comes SVG!
Building the Score Meter
1. Main Component
This is what the main structure would look like. It takes one prop: score, which is a number between 0 to 100.
export default function App({ score } : { score: number }) {
return (
<div className="score-wrap">
<div className="score">
<div className="score-bar">
<div className="placeholder">{progressBar(100)}</div>
<div className="score-circle">{progressBar(score, true)}</div>
</div>
<div className="score-value">
<div className="score-name">Score</div>
<div className="score-number">
{Math.round(score)}%
</div>
</div>
</div>
</div>
);
}
2. Progress Bar SVG
Now, let’s design the progress bar component, the svg used for both the placeholder and the fill.
function progressBar(widthPerc: number, gradient: boolean = false) {
const radius = 65;
const dashArray = (Math.PI * radius * widthPerc) / 100;
return (
<svg width="200" height="120">
<circle
cx="100"
cy="100"
r={radius}
fill="none"
strokeWidth="25"
strokeLinecap="round"
strokeDashoffset={-1 * Math.PI * radius}
strokeDasharray={`${dashArray} 10000`}
stroke={gradient ? "url(#score-gradient)" : "#e5e5e5"}
></circle>
{gradient && (
<defs>
<linearGradient id="score-gradient">
<stop offset="0%" stopColor="red" />
<stop offset="25%" stopColor="orange" />
<stop offset="100%" stopColor="green" />
</linearGradient>
</defs>
)}
</svg>
);
}
3. CSS
Add some CSS to position the text and the progress bars correctly.
.score-wrap {
display: flex;
justify-content: center;
margin-bottom: 20px;
}
.score {
width: 200px;
height: 120px;
position: relative;
overflow: hidden;
display: flex;
align-items: flex-end;
justify-content: center;
}
.score-bar {
position: absolute;
width: 100%;
height: 200%;
border-radius: 50%;
top: 0;
}
.score-bar .score-circle {
position: absolute;
top: 0;
}
.score-value {
margin-bottom: 5px;
text-align: center;
}
.score-name {
color: #777;
}
.score-number {
font-size: 25px;
font-weight: 600;
}
Progress Bar SVG Explanation
I think the main component and CSS parts are self-explanatory. I’ll explain how the SVG works step-by-step.
First, add a circle in an SVG canvas:
<svg width="200" height="120">
<circle
cx="100"
cy="100"
r="65"
></circle>
</svg>
Then, remove the fill and add a stroke.
<svg width="200" height="120">
<circle
cx="100"
cy="100"
r="65"
fill="none"
stroke-width="25"
stroke="#e5e5e5"
></circle>
</svg>
Then, most of the magic is done using stroke-dasharray and stroke-dashoffset.
stroke-dasharray
allows you to define a pattern for dashes and gaps. For example, if you add stroke-dasharray="20"
, you will see something like this.
Then, adding stroke-linecap="round"
gives you nice rounded dashes.
Let's do some calculations to set the stroke-dasharray
property correctly. In our React code, we use the following:
const dashArray = (Math.PI * radius * widthPerc) / 100;
// ...
strokeDasharray={`${dashArray} 10000`}
Math.PI * radius
is equal to half of the circumference of the circle. That's what we need for the placeholder of the progress bar (fill depends on the current score).
Note: the
10000
instrokeDasharray
is the gap. Because, we only need one dash, I used10000
to add a large gap to make sure only one dash is drawn. You can set it to anything larger than πr.
So,
Math.PI * radius
= 3.14 * 65
= 204.2
When we add it to our circle:
<svg width="200" height="120">
<circle
cx="100"
cy="100"
r="65"
fill="none"
stroke-width="25"
stroke="#e5e5e5"
stroke-dasharray="204.2 10000"
stroke-linecap="round"
></circle>
</svg>
we get this:
Finally, let's use stroke-dashoffset to change the position where the first dash is drawn. In our react code, we use strokeDashoffset={-1 * Math.PI * radius}
. So,
-1 * Math.PI * radius
= -3.14 * 65
= -204.2
So, our final SVG code looks like this:
<svg width="200" height="120">
<circle
cx="100"
cy="100"
r="65"
fill="none"
stroke-width="25"
stroke="#e5e5e5"
stroke-dasharray="204.2 10000"
stroke-linecap="round"
stroke-dashoffset="-204.2"
></circle>
</svg>
we get what we need!
This is the placeholder of the progress bar. In the fill, we use a gradient. Both are positioned on top of each other using CSS.
Final thoughts
While the score meter UI seems simple, creating it correctly takes some effort, especially if you are not very familiar with SVG. I hope this article helped you learn about SVG circles, dashes, and gaps. Feel free to reuse the code in your projects.
As mentioned earlier, I wrote this score meter for the SEO analyzer of Hyvor Blogs, which analyzes blog posts as you write and gives you feedback in real time. Here’s what it looks like for this post:
Check out Hyvor Blogs and all the features it provides to make blogging super easy.
If you have any feedback on the article, feel free to comment below.
Top comments (0)