Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Javascript

SVG Based Star Rating Control

4.68/5 (12 votes)
23 Apr 2012GPL35 min read 28.5K   432  
A Star Rating Control that uses SVG instead of image sprites

Introduction

Star Rating Controls are very common and ubiquitous in today's websites. They can be found on websites like amazon.com, netflix.com, and even codeproject.com. These controls allow users to rate an item on amazon, a movie on Netflix, or in case of codeproject - an article, such as the one you are currently reading. Have you wondered how they work? Most of them are based on image sprites and some css magic. Do a search in your favorite search engine to see what I mean. I think its time now to move onto controls that use SVG instead of image sprites. Most commonly used browsers today have full support for SVG. If your browser doesn't - why are you still using it? What are the advantages of a SVG based control? A SVG based control can be highly configurable as I will show you in this article. Do you want 5 stars or 10 stars? Do you want the stars to be colored yellow or red? The SVG control I am going to describe here allows you to easily configure these settings and customize the control to your requirements.

Background

It would be good for the reader to have some basic familiarity with JavaScript, HTML, and possibly SVG. I have tested the examples on IE9 and Chrome.

Using the Code

Let's jump right in. Download star_rating_demo.zip and unzip it - you should see four files in the unzipped folder: star_rating.js, demo.html, demo2.html, demo3.html.

All the code for the control is in one file star_rating.js. I am going to illustrate how you can use the control via 3 examples.

Example #1 demo.html

The entire code for this example is in the file demo.html and is copied below:

XML
<!doctype html>
<html>
<head>
</head>
<body>
    <div id="my_widget"
        class="rating_widget" 
        number_of_stars="5"
        initial_value="0"
        default_color="#ccc"
        initial_value_color="#ff0"
        final_value_color="#f00"
        captions="Poor, Didn't like it, OK, Good, Excellent!"
        onclick="clickHandler"
    ></div>
<script src="star_rating.js">    
</script>
<script>
    function clickHandler(sender, args)
    {
        alert('your rating: ' + args);
    }    
</script>
</body>
</html>   

Because of technical limitations, it is not possible to show working examples in this article. Instead screenshots will be provided. To see working examples, simply navigate to this page.

Screenshot of demo.html viewed in a web browser:

To see the full working example and what it can do, open demo.html in a web browser. You will see 5 stars colored grey (look for default_color="#ccc" in code above). If you mouse over the stars, they will change color to red (look for final_value_color="#f00" in code above), and you will also see corresponding text displayed (Poor, Didn't like it, OK, Good, Excellent!). If you click, the control will freeze, and the clickHandler function will get called. The rating is passed to the function as the args parameter. Look for the class="rating_widget" applied on the div - this is what causes the control to appear inside the div.

Example #2 demo2.html:

In this example we want to start out with a control that has 10 stars and has an initial rating of 7. Also I am going to illustrate another method of creating the control (this time in javascript, as opposed to markup in Example #1)

JavaScript
<!doctype html> 
<html> 
<head>
</head>
<body>    
<script src="star_rating.js">    
</script>
<script>
    var widget = new rating_widget(
    {
        number_of_stars: 10, // the total number of stars in the control
        initial_value: 7,    // initial rating
        default_color: 'rgb(200,200,200)',     // the grey stars 
        initial_value_color: 'rgb(255,255,0)', // the yellow stars
        final_value_color: 'rgb(255,0,0)',     // the red stars
        captions: '1,2,3,4,5,6,7,8,9,10',      // text to display to provide feedback to user                
        parentNode: document.body,   // parent node of the control
        onclick: function(sender, args)  // function to execute on mouse click
        { 
            alert('your rating: ' + args);
        }
    });            
</script>
</body>
</html>

Screenshot of demo2.html viewed in a web browser:

By changing the input parameters' values to the constructor of rating_widget, you can configure the control to your requirements - easily change colors, number of stars, and caption.

Example #3 demo 3.html:

In this final example, we will display a static control - one that does not allow the user to change the rating:

XML
<!doctype html>
<html>
<head>
</head>
<body>
    <div id="my_widget"
        class="rating_widget" 
        number_of_stars="5"
        initial_value="3.5"
        default_color="#ccc"
        initial_value_color="#ff0"
        final_value_color="#f00"        
        disabled="true"
    ></div>
<script src="star_rating.js">    
</script>
</body>
</html>

Screenshot of demo3.html viewed in a web browser:

Setting disabled="true" disables user from changing the rating of the control.

How it works

Let us now understand the inner workings of the control. Let us begin with this code inside star_rating.js

JavaScript
document.body.onload = function()
{
    var array = document.getElementsByClassName('rating_widget');
    for(var i = 0; i < array.length; i++)
    {
        var e = array[i];
        var widget = new rating_widget(
        {
            number_of_stars: e.getAttribute('number_of_stars') ? e.getAttribute('number_of_stars') : 5,
            initial_value: e.getAttribute('initial_value') ? e.getAttribute('initial_value') : 0,
            default_color: parseColor(e.getAttribute('default_color'), 'rgb(200,200,200)'),
            initial_value_color: parseColor(e.getAttribute('initial_value_color'), 'rgb(255,255,0)'),
            final_value_color: parseColor(e.getAttribute('final_value_color'), 'rgb(255,0,0)'),
            captions: e.getAttribute('captions'),
            captions_class_name: e.getAttribute('captions_class_name'),
            onclick: e.getAttribute('onclick'),
            disabled: e.getAttribute('disabled'),
            parentNode: e
        });
    }
};

The above function will be executed when the body of the html document is loaded. We get all the elements in the html document to which the class rating_widget has been applied, and store them into the variable array. Then we iterate through these elements, and create a star rating control for each element. The number of stars defaults to 5 unless there is an attribute number_of_stars on the element defining the number of stars you want in the control. initial_value is the initial rating you want to apply to the control. This would be 0 for an item that has never been rated before, but non-zero to reflect past ratings of the item. Interact with the control to understand what default_color, initial_value_color, and final_value_color mean. captions is an array of strings corresponding to the different ratings. You can control the appearance of the captions by styling them with CSS, and passing the CSS class name in the variable captions_class_name. onclick defines the function you want to be called when user clicks on the control. disabled if set to true disables the control in the sense that it loses its interactivity and freezes. This is useful when you want to use the control to display a rating, but don't want to allow users to be able to change the rating - e.g., if a user is not logged in. The parent node of the control is specified via parentNode: e.

Let us now dig deeper into the rating_widget function. The interesting part is in following code inside rating_widget:

JavaScript
for(var i = 0; i < n; i++)
{
    var star = document.createElementNS(svgns, "path");
    star.setAttributeNS(null, "d", coordinates);
    star.setAttributeNS(null, "transform", transform(0.3947432, 0, 0, 0.414816, 20*i, 1.159472));
    star.setAttributeNS(null, "style", style);
    svg_root.appendChild(star);
    var box = bbox(star);
    if (!disabled)
    {
        if (box) {    attachEventHandlers(box, boxes);    }
        else     {    attachEventHandlers(star, stars);    }
    }
    stars.push(star);
    boxes.push(box);
}
initial_state();

n is the number of stars we want to display. The SVG path to draw a star is stored in the variable coordinates. To draw multiple stars, we use the same coordinates over and over again, but apply a translation to them. 20*i is the x-component of the translation and is the crucial piece. The line thickness, its style - whether solid, dashed etc. is defined in the variable style. The function bbox calculates bounding box of the star. If the calculation is successful, we attach mouse event handlers to the bounding box of the star, else we attach handlers to the star itself. See this post for more details. The mouse event handlers is what gives interactivity to the control. The function initial_state initializes the control to the initial_value passed in the constructor.

Step through the code in the debugger to understand and investigate more as to how it works.

Conclusion

In this article I described a star rating control that uses SVG instead of image sprites and CSS. The control is highly configurable and number of stars, their color, and caption text can be easily changed. The control can be embedded in HTML markup (Examples #1, and #3), or can be dynamically instantiated in JavaScript (Example #2). I hope it is useful to some.

History

  • 4/20/2012 - Initial post.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)