Build a Star Rating System with TypeScript & CSS π
Star ratings are a common feature in modern user interfaces. In this article, we'll create an interactive star rating system using HTML, TypeScript, and CSS. This project demonstrates event handling, DOM manipulation, and leveraging CSS variables for styling.
π§ Project Setup
Our project consists of three main parts:
-
index.html
: The HTML structure and icons for the stars. -
style.css
: The CSS for styling the stars and messages. -
main.ts
: The TypeScript logic to make the system interactive.
ποΈ HTML Structure
Letβs start with the HTML file, index.html
. We use Font Awesome icons for the stars and a simple form to capture the user's rating:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<link href="/vite.svg" rel="icon" type="image/svg+xml"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Vite + TS</title>
<link crossorigin="anonymous" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css"
integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A=="
referrerpolicy="no-referrer" rel="stylesheet"/>
<link href="src/style.css" rel="stylesheet">
</head>
<body>
<div id="main">
<div id="container">
<h1>Are you Satisfied with our service ?</h1>
<form action="#" id="form">
<i class="fas fa-star star" star-rating="1"></i>
<i class="fas fa-star star" star-rating="2"></i>
<i class="fas fa-star star" star-rating="3"></i>
<i class="fas fa-star star" star-rating="4"></i>
<i class="fas fa-star star" star-rating="5"></i>
<input id="ratingInput" name="rating" style="display: none" type="number" value="3">
<input type="submit" value="Submit">
</form>
</div>
</div>
<div>
<p id="ratingValue">Click on the Stars to Rate them !</p>
</div>
<script src="/src/main.ts" type="module"></script>
</body>
</html>
π¨ CSS Styling
Hereβs the CSS file, style.css
, which styles the stars, buttons, and messages:
:root {
--color: green;
}
* {
background-color: #def4f0;
color: black;
text-align: center;
font-family: sans-serif;
font-size: 24px;
}
i.hoveredStars, i.clickedStars {
background-color: var(--color);
}
h1 {
font-size: 1.75em;
color: #444;
}
#main {
position: absolute;
margin: 0 auto;
top: 25%;
left: 50%;
}
#container {
position: relative;
left: -50%;
}
form {
margin-top: 65px;
}
form * {
display: inline-block;
}
form input[type="submit"] {
background-color: #400082;
color: white;
margin: 25px 25px 0 0;
padding: 10px;
border-radius: 15px;
transition: .1s ease-in-out;
}
form input[type="submit"]:hover {
background-color: #200052;
transform: translate(0, -3px);
}
form input[type="submit"]:hover {
transform: translate(0, 1px);
}
form i {
background-color: #bbb;
font-size: 1.2em;
transition: .1s ease-in-out;
color: white;
padding: 10px;
border-radius: 10px;
user-select: none;
}
#ratingValue {
position: absolute;
color: white;
bottom: 20px;
right: 20px;
background: rgba(0, 0, 0, 0.9);
padding: 12px 20px;
font-family: sans-serif;
border-radius: 5px;
}
Β π§ TypeScript Logic
Next, hereβs the TypeScript file, main.ts
, which handles user interactions with the stars:
const hues = [53, 30, 14, 344, 0]
let stars = document.querySelectorAll('.star') as NodeListOf<HTMLElement>
let ratingNumberElement = document.querySelector('#ratingInput') as HTMLInputElement
let rating = parseInt(ratingNumberElement.value)
let ratingPreview = document.querySelector('#ratingValue') as HTMLElement
const click = (element: HTMLElement) => {
ratingNumberElement.value = (element.getAttribute('star-rating') ?? 0).toString()
let newRating = parseInt(ratingNumberElement.value) as number
newRating--
judgeRating(ratingPreview, (newRating + 1))
// Assing Colors
assignStars(newRating, stars)
}
const hover = (element: HTMLElement) => {
let starRating = parseInt(element.getAttribute('star-rating') as string)
const starRatingCurrent = starRating - 1
let ratingIndex = rating - 1
if (starRatingCurrent > ratingIndex) {
assignColor(hues[starRatingCurrent], 'color')
}
Array.from(stars).slice(0, starRating).forEach(item => item.classList.add('hoveredStars'))
judgeRating(ratingPreview, starRating)
}
const hoverOut = (element: HTMLElement) => {
const clickStars = Array.from(stars).filter(item => !item.classList.contains('clickedStars'))
clickStars.forEach(item => item.classList.remove('hoveredStars'))
}
const getSiblings = (node: any) => [...node.parentNode.children].filter(c => c !== node)
const init = (listElements: NodeListOf<HTMLElement>, rating: number) => {
judgeRating(ratingPreview, rating)
rating--
// Assign Color
assignStars(rating, listElements)
}
const assignColor = (hue: number, assigned: string) => {
const sat = "85%",
val = "55%"
document.documentElement.style.setProperty(
"--" + assigned,
`hsl(${hue}, ${sat}, ${val})`
)
}
const judgeRating = (ratingSelector: HTMLElement, rating: number) => {
let reaction = (rating: number) => {
switch (rating) {
case 1:
return "Terrible !"
break;
case 2:
return "Eh, could've been better."
break;
case 3:
return "Okay, I guess."
break
case 4:
return "This is great !"
break;
case 5:
return "Wow ! As good as it gets !"
default:
return "ERR: you shouldn't be able to see this value..."
}
}
ratingSelector.innerText = "Rating: " + rating + ", " + reaction(rating)
}
const assignStars = (rating: number, list: NodeListOf<HTMLElement>) => {
let initStar = list[rating]
const siblings = getSiblings(initStar).filter(item => item.classList.contains('star'))
siblings.forEach(item => item.classList.remove('clickedStars'))
siblings.forEach(item => item.classList.remove('hoveredStars'))
assignColor(hues[rating], "color")
const prevAll = Array.from(list).slice(0, rating + 1)
prevAll.forEach(item => item.classList.add('clickedStars'))
}
// Init; Set up stars
init(stars, rating)
// Stars Events
stars.forEach(element => {
element.addEventListener('click', () => click(element))
element.addEventListener('mouseover', () => hover(element))
element.addEventListener('mouseout', () => hoverOut(element))
})
π Conclusion
Building a star rating system is a great exercise for learning DOM manipulation and working with user events. This system can be extended further by saving the userβs rating to a database, adding animations, or customizing the appearance.
Top comments (0)