DEV Community

Cover image for ๐Ÿš€ Build a Star Rating System with TypeScript & CSS
A0mineTV
A0mineTV

Posted on

๐Ÿš€ Build a Star Rating System with TypeScript & CSS

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:

  1. index.html: The HTML structure and icons for the stars.
  2. style.css: The CSS for styling the stars and messages.
  3. 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>
Enter fullscreen mode Exit fullscreen mode

๐ŸŽจ 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;
}
Enter fullscreen mode Exit fullscreen mode

ย ๐Ÿง  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))
})
Enter fullscreen mode Exit fullscreen mode

๐ŸŽ‰ 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)