
Hello ladies and gentlemen. Using this blog post I want to introduce and try myself as an author. To make this meeting more productive, I'm going to write about an instrument to rate something. We have a commonly used visual element such as "stars" in the Internet:
For example:
We also have a new trend - rate instruments of 3rd party services such as Facebook and Vkontakte "Likes", that allow to rate the blog post only in the positive way. We will talk about them another time, think if that trend is good or bad. Now I'm going to show a few implementations of traditional stars.
First, we need to get an image of the star itself. It's also good to have its different states such as the "hover" and "active" states. That's why I drew such star and merged its states into one sprite.
After this we can start working on the HTML markup.
Simple rating
I propose to start from the simplest variant - 5 completed stars. If we do hover on one star, then the previous ones must change their state, too. A semantically right element for our use case is an ordered list. Why ordered? It's quite simple. The stars have own "weight": from 1 to 5 (depends on how many stars you wish to use :)).
The structure is simple:
<ol id="rating"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> </ol>
Let's think how we can highlight previous elements if we do "hover" on one star. Probably many of you know the "~" CSS selector. For those who don't, here is a short explanation: it allows to select all following in the DOM elements (siblings), it works as "+", but for all elements. How can this help us? We need to select the previous elements, not the following ones... Here we will use our savvy: we will sort the elements in the reverse order and then apply the `float: right` style to them. In the end we will get the following DOM structure:
<ol id="rating"> <li>5</li> <li>4</li> <li>3</li> <li>2</li> <li>1</li> </ol>
Then we just add the following styles:
.rating { list-style: none; margin: 0; padding: 0; width: 100px; height: 20px; } .rating li { display: block; width: 20px; height: 20px; float: right; /* we need this style to sort the `li` elements in the reverse order */ text-indent: -9999px; /* hide text */ cursor: pointer; background: url("stars.png"); }
Now we have to add styles for the "hover" state:
.rating li:hover, .rating li:hover ~ li { background-position: 0 -20px; }
The second row above changes background of all visually previous elements (they are still the following elements in the DOM structure):
The simplest way to show the average rating is to round it to the closest integer number and add a necessary class to a particular element of the ordered list:
<ol id="rating"> <li>5</li> <li class=”active”>4</li> <li>3</li> <li>2</li> <li>1</li> </ol>
Then we just need to add a few CSS rules:
.rating li.active, .rating li.active ~ li { background-position: 0 -40px; }
The problem is that our previous solution isn't informative enough. It's better to show the rating as percentage. Let's add one more element to the list. That element will be visible only if the list has a special class – in our case it's the .show-current class. The modified DOM structure looks like:
<ol class="rating show-current"> <li>5</li> <li>4</li> <li>3</li> <li>2</li> <li>1</li> <li class="current"><span></span></li> </ol>
To make our stars alive, we need to add some JavaScript code:
var $rating = $('.rating'); $rating.on('mouseover', function() { $(this).removeClass('show-current'); }).on('mouseleave', function() { $(this).addClass('show-current'); }); $('li', $rating).on('click', function() { alert('User selected ' + $(this).text()); //Ofcourse, here you need/must put more complicated code. For example, you can send the result of the voting to your server via ajax, show the new state to users });
Before we can use our rating in production, we may do a few optimizations:
-
we can remove one HTTP request by embedding the image/sprite as data:URL in CSS
-
To add some animations to our rating we can:
-
set up the initial width to 0 pixels
-
add css-transition to animate elements using the native capabilites of a browser
-
change the current width within some time intervals using JavaScript
-
Ofcourse, we can think about more improvements, but I leave it for a reader.
"Mottled" rating
It's not rare to see half-highlighted stars on sites. To create such rating, we can use the same approach, but we will need to modify our styles to the new requirements. I won't describe everything in details, just want to show the final result with some meaningful comments.
The rating structure:
<ol class="rating-half show-current"> <li>10</li> <li>9</li> <li>8</li> <li>7</li> <li>6</li> <li>5</li> <li>4</li> <li>3</li> <li>2</li> <li>1</li> <li class="current"><span></span></li> </ol>
The styles are a little bit more complicated:
li { display: block; width: 10px; height: 20px; float: right; text-indent: -9999px; cursor: pointer; background: url("img/stars.png") no-repeat; } li.current { display: none; } ul.show-current{ position: relative; } ul.show-current li { cursor: default; } ul.show-current li.current { position: absolute; top: 0; left: 0; display: block; width: 0; background-position: 0 -60px; background-repeat: repeat-x; } ul.show-current li.current:hover, ul.show-current li.current:hover ~ li { background-position: 0 -60px; } ul.show-current li.current span { display: block; height: 20px; width: 0; background: inherit; background-position: 0 -40px; } /* odd elements элементы */ li:nth-child(odd) { background-position: -10px 0; } /* odd elements with `hover` and all previous odd elements */ li:nth-child(even):hover, li:nth-child(even):hover ~ li:nth-child(even){ background-position: 0 -20px; } /* odd elements with `hover` and all previous even elements */ li:nth-child(even):hover ~ li:nth-child(odd){ background-position: -10px -20px; } /* even elements with `hover` and all previous even elements */ li:nth-child(odd):hover, li:nth-child(odd):hover ~ li:nth-child(odd) { background-position: -10px -20px; } /* even elements with `hover` and all previous odd elements */ li:nth-child(odd):hover ~ li:nth-child(even) { background-position: 0 -20px; }
Conclusions:
Support in browsers:
Supported by all modern browsers, plus:
- the Simple rating - >= IE8
- the Mottled rating - >= IE9 (due to usage of :nth-child)
That's all I wanted to tell you. Ofcourse, this is not the only one possible way of implementation, and you can find many others in the Internet. Even though there are the two main approaches: one that uses inputs and another with styles for each star, I think my approach can be suitable, too (especially after the IE8 death). See you around.
ps. (in the demo repository you can find the `less` version of the CSS)