Star Rating — Make SVG Great Again
A walkthrough of how I built a generic, highly customizable Vue star rating component (like Google Play’s rating mechanism).


If you just want to see the Github library you can click here.
A Web Dev Reality
During our work as web developers we occasionally get a weird requirement, one that we had never encountered before — as professionals we begin by researching and looking for a pre-built library that will satiate our needs, as the production-reality requires of us to find a good-enough solution and fast.
Research First
Assuming we find such library, we move on to our next challenge. In my case it was the quite the opposite. I indeed received a weird requirement — build a five-star mechanism for a sort of app store screen. I had never encountered this need before and like all of us — I had to adapt, move fast and deliver.
I began by prospecting various solutions to the problem, downloaded and played with a few sandboxes just to realize that none of the existing solutions is dynamic enough and allows me enough style customization to consider for this task in hand.
Improvise. Adapt, Overcome

I then thought of how I should implement the challenge — html Canvas? SVG? Something else? I wanted my solution to be scalable, dynamic and customizable that was the original motivation to go and build it my own — After some considerations I decided — SVG.
I built a very first and improvised component utilizing a prefixed values of a 5-armed star I found using a <polygon> element:
<polygon points="100,10 40,198 190,78 10,78 160,198" style="fill-rule:nonzero;"/>
This way I could focus on building all the customization logic of colors, sizes gradients and the algorithm that draws the stars according to the binded rating score in the range of 0.0–5.0.
A Star-Drawing Algorithm
As seen in the code block above, an SVG Polygon element has a points
attribute that receives a string of x,y coordinates —it’s built-in drawing algorithm determines the drawing path between each x,y
coordinates that eventually produce the desired polygon:
points="100,10 40,198 190,78 10,78 160,198"

Once done with building the logic I realized it was not possible to alter the star’s width/height dynamically due to it’s hard-coded values — I had to draw the star programmatically in order to achieve a more dynamic star with width & height that are easily adjustable.
Planning an Algorithm
I started thinking and playing around with drawing triangles, circles, triangles and stars within circles — I even refreshed my memory of Sine, Cosine & Tangent from high school and played around with calculating angles and points. My goal was to find some kind of pattern before I start writing my algorithm.
Pattern 1 —Outer Radius Marks Outer Star Points

Pattern 2 — Inner Radius Marks Inner Star Points

So if we layer up the two circles on the same star we than see all of our x,y points with the two mentioned patterns like so:

Pattern 3 — Inner-to-Outer-radius Ratio
To simplify things and start defining the unknowns I decided to determine that the outer radius is exactly two times the inner radius — I thought it would be best to just get up and running with a basic 1-by-1 ratio between the circles like so:

Pattern 4 — A set drawing angle that accumulates in equal proportion to determine next X, Y point
First we have to understand how the first point is calculated and drawn — from the (centerX, centerY)
which is the center of the circle as shown in the drawing — we use the same angle of each equal part of the circle calculated by let angle = (Math.PI / innerCirclePoints) // 5 points
with it we then utilize cos
& sin
and a given radius
, in our case it could be 50% of the starWidth/height, to get the (x,y)
of the first point from which the star would be drawn from:

And from this point onwards we accumulate on both X,Y — to guide the polygon inner-algorithm how to draw our polygon which ends up in this behavior — (Click the Pentagram example to get a feeling of how it works)
Conclusion
We have 5 inner x,y points and 5 outer x,y points that would have to be mapped one after another from the first drawing point to the last alternatively — big X,Y small x,y and so on like so "X,Y x,y X,Y x,y..."
The only difference between the above way of drawing the star and our way is that in our way in each even
iteration (i.e 0, 2, 4…) we draw a X,Y
point of the outer circle and every odd
iteration (i.e 1, 3, 5…) we draw a x,y
point.
Lets get coding!
All this planning really makes me wanna start coding already! — so I began with a for loop that will iterate over the five points
amount multiplied by 2–Which is 10 x,y points that compose the star. I then binded a method to the points attribute and began writing the algorithm:
<polygon :points="getStarPoints" style="fill-rule:nonzero;" />
I then stepped forth to write the first version of the algorithm — getStarPoints
method which provides the relevant properties for calcStarPoints
:
getStarPoints: function (){
let centerX = 0;
let centerY = 0;
let innerCirclePoints = 5; // a 5 point star
// this.style.starWidth --> this is the beam length of each
// side of the SVG square that holds the star
let innerRadius = this.style.starWidth / innerCirclePoints; let innerOuterRadiusRatio = 2; // outter circle is x2 the innerlet outerRadius = innerRadius * innerOuterRadiusRatio;
return this.calcStarPoints(centerX, centerY, innerCirclePoints, innerRadius, outerRadius);
},
The calcStarPoints
method that will calculate each x,y point in a single-star according to the total x,y points(10 in our case) — and will generate the polygon from point to point
calcStarPoints(centerX, centerY, innerCirclePoints, innerRadius, outerRadius) {
const angle = (Math.PI / innerCirclePoints);
const angleOffsetToCenterStar = 0;
const totalPoints = innerCirclePoints * 2; // 10 in a 5-points star
let points = '';
for (let i = 0; i < totalPoints; i++) {
let isEvenIndex = i % 2 == 0;
let r = isEvenIndex ? outerRadius : innerRadius;
let currX = centerX + Math.cos(i * angle + angleOffsetToCenterStar ) * r;
let currY = centerY + Math.sin(i * angle + angleOffsetToCenterStar) * r;
points += currX + ',' + currY + ' ';
}
return points;
},
First Drawing Impressions
And the result can be seen — although its not perfect we achieved greatness!

It is clear that our star is being drawn from the default x,y properties of an SVG element — 0,0
a simple way to make the star render in the middle of the svg would be to always give it 50% of it’s height as y property & 50% of it’s width as x property — If we want it to support dynamic width/height binded from the parent component utilizing this component the solution would be to change just these two lines to the following
getStarPoints: function() {
let centerX = this.style.starWidth / 2; // e.g 100
let centerY = this.style.starHeight / 2; // e.g 100
...
}
And the result reveals itself — great success!

Chubby stars slim stars — And everything between
Some of us software developers have a tendency to eat unhealthy foods(myself included), sweets, drinks and minimize the amount of sport we are doing with all kinds of excuses — well, it could take time until every developer in the world would adopt a more healthy approach — but hey! in a flick of a the ratio property from x2 to x2.5 to the inner circle we can now make our friendly star a bit more in shape ;)
getStarPoints: function() {
let innerOuterRadiusRatio = 2.5; // set star sharpness/chubyness
let outerRadius = innerRadius * innerOuterRadiusRatio;
...
}
Ahaa that’s much better now:

Rotation Headaches
The next challenge I faced was to straighten up the star so it would be positioned vertically relative to the screen. My instinct told me hey! that’s not a problem just transform rotate it and all your problems would be solved. And so I went on and tested it —
.star-svg {
transform: rotate(55deg);
}

Holy smokes! the starts are vertically aligned — but the gradient seesm to be broken… So I though well lets try and rotate the gradient itself <linearGradient gradientTransform="rotate(55)">
which lead to even more troubles due to the fact that the gradient sets its center according to the original origin the SVG was drawn in (the transform rotate on the .star-svg did not change the original origin)—

Aha Moment! — Mid-Calculation Rotating
After these few test I realized that I need to change my approach — the tests did make me better understand how <linearGradient>
is working and i realized that If I cannot change the origin after rendering, I must change it during the drawing/creation of each SVG element — during calculations.
So inside my calcStarPoints()
method I declared a certain angle offset and had my cos
and sin
trigo functions consider this offset with each calculation of a X or Y of a certain draw point in the star like so —
calcStarPoints(...) {
...
let angleOffsetToCenterStar = 60;
...
for (let i = 0; i < totalPoints; i++) {
let currX = centerX + Math.cos(i * angle + angleOffsetToCenterStar) * r;
let currY = centerY + Math.sin(i * angle + angleOffsetToCenterStar) * r;
...
}
}
And Voilà! now the star is drawn in a perfectly vertical way and the linear gradient’s origin is the exact left-to-right origin of the SVG —

Summary
I would appreciate feedback, claps, shares & a github star for the five-star rating library :) you could of course find all code, demo & a live sandbox to play around with as well as organized API docs for convenience of use.
More Recommended Posts by me about Product Design, UX & Frontend:
- How I recreated Facebook’s microinteraction for feature discovery
- Medium Clap Recreated in Vanilla JS — A full Walkthrough Guide
- Star Rating — Make SVG Great Again — A step-by-step code tutorial
- Art of Code — Why You Should Write more Pseudo Code
More open-source Vue Components:
- Vue Dynamic Dropdown — A Customizable, easy-to-use elegant dropdown
- Vue Dynamic Star Rating — A dynamic vue star rating component(similar to google play)
I am Jonathan Doron, a Web Developer with great passion for User Centric Frontend, and modular client architecture.
What thrills me these days is exploring the ocean of Interaction Design more specifically of Microinteractions and their impact on our lives. I do it by recreating existing interactions as well as designing my own interactions along my quest to deepen my knowledge in the field.
You are welcome to follow, tweet or message me freely with any questions, feedback or suggestions! — Twitter
Reviewers
Thanks a whole lot for the help of these great people who helped review and feedback the article drafts, Your amazing! ;)— Dvir Hazout Ofir Ovadia Matan Yosef Elad Shechter