This Vanilla JS code snippet helps you to create SVG star rating animation with radio inputs. The five star rating comes with 5 different levels of feedbacks with stars and text bottom of them.
One star rating indicates terrible experience while five star rating indicates excellent experience. User can select the star and this code snippet animates the star ✨ and display rating feedback accordingly.
How to Create Star Rating Animation using SVG and Vanilla JS
1. First of all, load the Google Fonts CSS into the head tag of your HTML document.
<link rel='stylesheet' href='https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500&display=swap'>
2. After that, create the HTML form element with a class name “rating”, place radio inputs with unique ID attributes, and define SVG elements as define in the below HTML code:
<form class="rating"> <div class="rating__stars"> <input id="rating-1" class="rating__input rating__input-1" type="radio" name="rating" value="1"> <input id="rating-2" class="rating__input rating__input-2" type="radio" name="rating" value="2"> <input id="rating-3" class="rating__input rating__input-3" type="radio" name="rating" value="3"> <input id="rating-4" class="rating__input rating__input-4" type="radio" name="rating" value="4"> <input id="rating-5" class="rating__input rating__input-5" type="radio" name="rating" value="5"> <label class="rating__label" for="rating-1"> <svg class="rating__star" width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"> <g transform="translate(16,16)"> <circle class="rating__star-ring" fill="none" stroke="#000" stroke-width="16" r="8" transform="scale(0)" /> </g> <g stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <g transform="translate(16,16) rotate(180)"> <polygon class="rating__star-stroke" points="0,15 4.41,6.07 14.27,4.64 7.13,-2.32 8.82,-12.14 0,-7.5 -8.82,-12.14 -7.13,-2.32 -14.27,4.64 -4.41,6.07" fill="none" /> <polygon class="rating__star-fill" points="0,15 4.41,6.07 14.27,4.64 7.13,-2.32 8.82,-12.14 0,-7.5 -8.82,-12.14 -7.13,-2.32 -14.27,4.64 -4.41,6.07" fill="#000" /> </g> <g transform="translate(16,16)" stroke-dasharray="12 12" stroke-dashoffset="12"> <polyline class="rating__star-line" transform="rotate(0)" points="0 4,0 16" /> <polyline class="rating__star-line" transform="rotate(72)" points="0 4,0 16" /> <polyline class="rating__star-line" transform="rotate(144)" points="0 4,0 16" /> <polyline class="rating__star-line" transform="rotate(216)" points="0 4,0 16" /> <polyline class="rating__star-line" transform="rotate(288)" points="0 4,0 16" /> </g> </g> </svg> <span class="rating__sr">1 star—Terrible</span> </label> <label class="rating__label" for="rating-2"> <svg class="rating__star" width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"> <g transform="translate(16,16)"> <circle class="rating__star-ring" fill="none" stroke="#000" stroke-width="16" r="8" transform="scale(0)" /> </g> <g stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <g transform="translate(16,16) rotate(180)"> <polygon class="rating__star-stroke" points="0,15 4.41,6.07 14.27,4.64 7.13,-2.32 8.82,-12.14 0,-7.5 -8.82,-12.14 -7.13,-2.32 -14.27,4.64 -4.41,6.07" fill="none" /> <polygon class="rating__star-fill" points="0,15 4.41,6.07 14.27,4.64 7.13,-2.32 8.82,-12.14 0,-7.5 -8.82,-12.14 -7.13,-2.32 -14.27,4.64 -4.41,6.07" fill="#000" /> </g> <g transform="translate(16,16)" stroke-dasharray="12 12" stroke-dashoffset="12"> <polyline class="rating__star-line" transform="rotate(0)" points="0 4,0 16" /> <polyline class="rating__star-line" transform="rotate(72)" points="0 4,0 16" /> <polyline class="rating__star-line" transform="rotate(144)" points="0 4,0 16" /> <polyline class="rating__star-line" transform="rotate(216)" points="0 4,0 16" /> <polyline class="rating__star-line" transform="rotate(288)" points="0 4,0 16" /> </g> </g> </svg> <span class="rating__sr">2 stars—Bad</span> </label> <label class="rating__label" for="rating-3"> <svg class="rating__star" width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"> <g transform="translate(16,16)"> <circle class="rating__star-ring" fill="none" stroke="#000" stroke-width="16" r="8" transform="scale(0)" /> </g> <g stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <g transform="translate(16,16) rotate(180)"> <polygon class="rating__star-stroke" points="0,15 4.41,6.07 14.27,4.64 7.13,-2.32 8.82,-12.14 0,-7.5 -8.82,-12.14 -7.13,-2.32 -14.27,4.64 -4.41,6.07" fill="none" /> <polygon class="rating__star-fill" points="0,15 4.41,6.07 14.27,4.64 7.13,-2.32 8.82,-12.14 0,-7.5 -8.82,-12.14 -7.13,-2.32 -14.27,4.64 -4.41,6.07" fill="#000" /> </g> <g transform="translate(16,16)" stroke-dasharray="12 12" stroke-dashoffset="12"> <polyline class="rating__star-line" transform="rotate(0)" points="0 4,0 16" /> <polyline class="rating__star-line" transform="rotate(72)" points="0 4,0 16" /> <polyline class="rating__star-line" transform="rotate(144)" points="0 4,0 16" /> <polyline class="rating__star-line" transform="rotate(216)" points="0 4,0 16" /> <polyline class="rating__star-line" transform="rotate(288)" points="0 4,0 16" /> </g> </g> </svg> <span class="rating__sr">3 stars—OK</span> </label> <label class="rating__label" for="rating-4"> <svg class="rating__star" width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"> <g transform="translate(16,16)"> <circle class="rating__star-ring" fill="none" stroke="#000" stroke-width="16" r="8" transform="scale(0)" /> </g> <g stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <g transform="translate(16,16) rotate(180)"> <polygon class="rating__star-stroke" points="0,15 4.41,6.07 14.27,4.64 7.13,-2.32 8.82,-12.14 0,-7.5 -8.82,-12.14 -7.13,-2.32 -14.27,4.64 -4.41,6.07" fill="none" /> <polygon class="rating__star-fill" points="0,15 4.41,6.07 14.27,4.64 7.13,-2.32 8.82,-12.14 0,-7.5 -8.82,-12.14 -7.13,-2.32 -14.27,4.64 -4.41,6.07" fill="#000" /> </g> <g transform="translate(16,16)" stroke-dasharray="12 12" stroke-dashoffset="12"> <polyline class="rating__star-line" transform="rotate(0)" points="0 4,0 16" /> <polyline class="rating__star-line" transform="rotate(72)" points="0 4,0 16" /> <polyline class="rating__star-line" transform="rotate(144)" points="0 4,0 16" /> <polyline class="rating__star-line" transform="rotate(216)" points="0 4,0 16" /> <polyline class="rating__star-line" transform="rotate(288)" points="0 4,0 16" /> </g> </g> </svg> <span class="rating__sr">4 stars—Good</span> </label> <label class="rating__label" for="rating-5"> <svg class="rating__star" width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"> <g transform="translate(16,16)"> <circle class="rating__star-ring" fill="none" stroke="#000" stroke-width="16" r="8" transform="scale(0)" /> </g> <g stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <g transform="translate(16,16) rotate(180)"> <polygon class="rating__star-stroke" points="0,15 4.41,6.07 14.27,4.64 7.13,-2.32 8.82,-12.14 0,-7.5 -8.82,-12.14 -7.13,-2.32 -14.27,4.64 -4.41,6.07" fill="none" /> <polygon class="rating__star-fill" points="0,15 4.41,6.07 14.27,4.64 7.13,-2.32 8.82,-12.14 0,-7.5 -8.82,-12.14 -7.13,-2.32 -14.27,4.64 -4.41,6.07" fill="#000" /> </g> <g transform="translate(16,16)" stroke-dasharray="12 12" stroke-dashoffset="12"> <polyline class="rating__star-line" transform="rotate(0)" points="0 4,0 16" /> <polyline class="rating__star-line" transform="rotate(72)" points="0 4,0 16" /> <polyline class="rating__star-line" transform="rotate(144)" points="0 4,0 16" /> <polyline class="rating__star-line" transform="rotate(216)" points="0 4,0 16" /> <polyline class="rating__star-line" transform="rotate(288)" points="0 4,0 16" /> </g> </g> </svg> <span class="rating__sr">5 stars—Excellent</span> </label> <p class="rating__display" data-rating="1" hidden>Terrible</p> <p class="rating__display" data-rating="2" hidden>Bad</p> <p class="rating__display" data-rating="3" hidden>OK</p> <p class="rating__display" data-rating="4" hidden>Good</p> <p class="rating__display" data-rating="5" hidden>Excellent</p> </div> </form>
3. Style the star rating using the following CSS. You can set the custom values for star fill color and transition values according to your needs.
* { border: 0; box-sizing: border-box; margin: 0; padding: 0; } :root { --bg: #e3e4e8; --fg: #17181c; --primary: #255ff4; --yellow: #f4a825; --yellow-t: rgba(244, 168, 37, 0); --bezier: cubic-bezier(0.42,0,0.58,1); --trans-dur: 0.3s; font-size: calc(24px + (30 - 24) * (100vw - 320px) / (1280 - 320)); } body { background-color: var(--bg); color: var(--fg); font: 1em/1.5 "DM Sans", sans-serif; display: flex; height: 100vh; transition: background-color var(--trans-dur), color var(--trans-dur); } .rating { margin: auto; } .rating__display { font-size: 1em; font-weight: 500; min-height: 1.25em; position: absolute; top: 100%; width: 100%; text-align: center; } .rating__stars { display: flex; padding-bottom: 0.375em; position: relative; } .rating__star { display: block; overflow: visible; pointer-events: none; width: 2em; height: 2em; } .rating__star-ring, .rating__star-fill, .rating__star-line, .rating__star-stroke { animation-duration: 1s; animation-timing-function: ease-in-out; animation-fill-mode: forwards; } .rating__star-ring, .rating__star-fill, .rating__star-line { stroke: var(--yellow); } .rating__star-fill { fill: var(--yellow); transform: scale(0); transition: fill var(--trans-dur) var(--bezier), transform var(--trans-dur) var(--bezier); } .rating__star-stroke { stroke: #c7cad1; transition: stroke var(--trans-dur); } .rating__label { cursor: pointer; padding: 0.125em; } .rating__label--delay1 .rating__star-ring, .rating__label--delay1 .rating__star-fill, .rating__label--delay1 .rating__star-line, .rating__label--delay1 .rating__star-stroke { animation-delay: 0.05s; } .rating__label--delay2 .rating__star-ring, .rating__label--delay2 .rating__star-fill, .rating__label--delay2 .rating__star-line, .rating__label--delay2 .rating__star-stroke { animation-delay: 0.1s; } .rating__label--delay3 .rating__star-ring, .rating__label--delay3 .rating__star-fill, .rating__label--delay3 .rating__star-line, .rating__label--delay3 .rating__star-stroke { animation-delay: 0.15s; } .rating__label--delay4 .rating__star-ring, .rating__label--delay4 .rating__star-fill, .rating__label--delay4 .rating__star-line, .rating__label--delay4 .rating__star-stroke { animation-delay: 0.2s; } .rating__input { -webkit-appearance: none; appearance: none; } .rating__input:hover ~ [data-rating]:not([hidden]) { display: none; } .rating__input-1:hover ~ [data-rating="1"][hidden], .rating__input-2:hover ~ [data-rating="2"][hidden], .rating__input-3:hover ~ [data-rating="3"][hidden], .rating__input-4:hover ~ [data-rating="4"][hidden], .rating__input-5:hover ~ [data-rating="5"][hidden], .rating__input:checked:hover ~ [data-rating]:not([hidden]) { display: block; } .rating__input-1:hover ~ .rating__label:first-of-type .rating__star-stroke, .rating__input-2:hover ~ .rating__label:nth-of-type(-n + 2) .rating__star-stroke, .rating__input-3:hover ~ .rating__label:nth-of-type(-n + 3) .rating__star-stroke, .rating__input-4:hover ~ .rating__label:nth-of-type(-n + 4) .rating__star-stroke, .rating__input-5:hover ~ .rating__label:nth-of-type(-n + 5) .rating__star-stroke { stroke: var(--yellow); transform: scale(1); } .rating__input-1:checked ~ .rating__label:first-of-type .rating__star-ring, .rating__input-2:checked ~ .rating__label:nth-of-type(-n + 2) .rating__star-ring, .rating__input-3:checked ~ .rating__label:nth-of-type(-n + 3) .rating__star-ring, .rating__input-4:checked ~ .rating__label:nth-of-type(-n + 4) .rating__star-ring, .rating__input-5:checked ~ .rating__label:nth-of-type(-n + 5) .rating__star-ring { animation-name: starRing; } .rating__input-1:checked ~ .rating__label:first-of-type .rating__star-stroke, .rating__input-2:checked ~ .rating__label:nth-of-type(-n + 2) .rating__star-stroke, .rating__input-3:checked ~ .rating__label:nth-of-type(-n + 3) .rating__star-stroke, .rating__input-4:checked ~ .rating__label:nth-of-type(-n + 4) .rating__star-stroke, .rating__input-5:checked ~ .rating__label:nth-of-type(-n + 5) .rating__star-stroke { animation-name: starStroke; } .rating__input-1:checked ~ .rating__label:first-of-type .rating__star-line, .rating__input-2:checked ~ .rating__label:nth-of-type(-n + 2) .rating__star-line, .rating__input-3:checked ~ .rating__label:nth-of-type(-n + 3) .rating__star-line, .rating__input-4:checked ~ .rating__label:nth-of-type(-n + 4) .rating__star-line, .rating__input-5:checked ~ .rating__label:nth-of-type(-n + 5) .rating__star-line { animation-name: starLine; } .rating__input-1:checked ~ .rating__label:first-of-type .rating__star-fill, .rating__input-2:checked ~ .rating__label:nth-of-type(-n + 2) .rating__star-fill, .rating__input-3:checked ~ .rating__label:nth-of-type(-n + 3) .rating__star-fill, .rating__input-4:checked ~ .rating__label:nth-of-type(-n + 4) .rating__star-fill, .rating__input-5:checked ~ .rating__label:nth-of-type(-n + 5) .rating__star-fill { animation-name: starFill; } .rating__input-1:not(:checked):hover ~ .rating__label:first-of-type .rating__star-fill, .rating__input-2:not(:checked):hover ~ .rating__label:nth-of-type(2) .rating__star-fill, .rating__input-3:not(:checked):hover ~ .rating__label:nth-of-type(3) .rating__star-fill, .rating__input-4:not(:checked):hover ~ .rating__label:nth-of-type(4) .rating__star-fill, .rating__input-5:not(:checked):hover ~ .rating__label:nth-of-type(5) .rating__star-fill { fill: var(--yellow-t); } .rating__sr { clip: rect(1px, 1px, 1px, 1px); overflow: hidden; position: absolute; width: 1px; height: 1px; } @media (prefers-color-scheme: dark) { :root { --bg: #17181c; --fg: #e3e4e8; } .rating { margin: auto; } .rating__star-stroke { stroke: #454954; } } @keyframes starRing { from, 20% { animation-timing-function: ease-in; opacity: 1; r: 8px; stroke-width: 16px; transform: scale(0); } 35% { animation-timing-function: ease-out; opacity: 0.5; r: 8px; stroke-width: 16px; transform: scale(1); } 50%, to { opacity: 0; r: 16px; stroke-width: 0; transform: scale(1); } } @keyframes starFill { from, 40% { animation-timing-function: ease-out; transform: scale(0); } 60% { animation-timing-function: ease-in-out; transform: scale(1.2); } 80% { transform: scale(0.9); } to { transform: scale(1); } } @keyframes starStroke { from { transform: scale(1); } 20%, to { transform: scale(0); } } @keyframes starLine { from, 40% { animation-timing-function: ease-out; stroke-dasharray: 1 23; stroke-dashoffset: 1; } 60%, to { stroke-dasharray: 12 12; stroke-dashoffset: -12; } }
4. Finally, add the following JavaScript function before closing of the body tag to activate the star rating animation.
window.addEventListener("DOMContentLoaded",() => { const starRating = new StarRating("form"); }); class StarRating { constructor(qs) { this.ratings = [ {id: 1, name: "Terrible"}, {id: 2, name: "Bad"}, {id: 3, name: "OK"}, {id: 4, name: "Good"}, {id: 5, name: "Excellent"} ]; this.rating = null; this.el = document.querySelector(qs); this.init(); } init() { this.el?.addEventListener("change",this.updateRating.bind(this)); // stop Firefox from preserving form data between refreshes try { this.el?.reset(); } catch (err) { console.error("Element isn’t a form."); } } updateRating(e) { // clear animation delays Array.from(this.el.querySelectorAll(`[for*="rating"]`)).forEach(el => { el.className = "rating__label"; }); const ratingObject = this.ratings.find(r => r.id === +e.target.value); const prevRatingID = this.rating?.id || 0; let delay = 0; this.rating = ratingObject; this.ratings.forEach(rating => { const { id } = rating; // add the delays const ratingLabel = this.el.querySelector(`[for="rating-${id}"]`); if (id > prevRatingID + 1 && id <= this.rating.id) { ++delay; ratingLabel.classList.add(`rating__label--delay${delay}`); } // hide ratings to not read, show the one to read const ratingTextEl = this.el.querySelector(`[data-rating="${id}"]`); if (this.rating.id !== id) ratingTextEl.setAttribute("hidden",true); else ratingTextEl.removeAttribute("hidden"); }); } }
That’s all! hopefully, you have successfully created star rating animation using SVG and Vanilla JavaScript. If you have any questions or suggestions, feel free to comment below.
Similar Code Snippets:
I code and create web elements for amazing people around the world. I like work with new people. New people new Experiences.
I truly enjoy what I’m doing, which makes me more passionate about web development and coding. I am always ready to do challenging tasks whether it is about creating a custom CMS from scratch or customizing an existing system.