That’s something I thought of quite some time ago, but didn’t have the chance to use anywhere – until now! This will appear in an upcoming project that will probably be released in the next few weeks. I will show you how to make an IMDB-like star rating interface where you rate a product by giving it a number of stars (1 to 5). When you’re not hovering the stars, the control displays the current rating of the product (say, 3.5 stars). When you move the mouse over one of the stars, it is highlighted as well as all other stars to the left of it, so that the number of highlighted stars corresponds to the rating you’re going to give for that product. When you click the star, your rating is processed (which is out of the scope of this article). Except for handling the mouse clicks, everything else is pure CSS – including the mouse-over effects.
Here is what I’m talking about:
My goal was to make the control relying solely on CSS for the mouse-over effects, using the :hover pseudoclass to achieve what normally would be done with JavaScript. I decided not to bother with full support of IE6 – although the control is still functional when being clicked, it won’t highlight the stars as you move the mouse over them. That’s due to the well-known fact that :hover works only on A elements in IE6 – bummer…
I also wanted to be able to specify stepless values for the current rating of the product, instead of being restricted to multiples of one star (or half a star, as commonly seen over the web). Achieving this also has the benefit of not having to use more than 3 images – one for each star state. Of course, nothing stops you from rounding the rating percent for aestetic reasons, but it’s nice not to be limited to just 5 values.
If you don’t want to dig through the code to get the idea, here it is:
- The outermost container is a DIV element with class
.starsand isfloat-ing to theleft; the grey star is used as its background and is repeated along the X axis so that 5 of them are displayed. - Inside the container there’s a DIV element with class
.ratingthat has its width set in percent that correspond to the current rating we want to display (for 3.5 out of 5 stars, that would be 70%). Its background is the yellow star, repeated along the X axis. Since that DIV is absolutely positioned, it sticks to the left edge of its container – as its width increases, it covers more and more of the grey stars and they become yellow. At 100%, all stars are yellow. - The mouse-over effect is achieved with 5 DIV elements nested into each other (see the markup below). Each of them has a fixed height and left padding corresponding to the size of one star image (in this case, 32px). That padding pushes the left edge of each nested DIV 32px to the right, making it smaller. Although you don’t see it, the 5th orange star is actually all 5 of those DIVs stacked on top of each other (and if you look closely, that’s the reason why I’m not using star images with alpha transparency
). The first DIV spans over stars 1-5, the second one is over stars 2-5, and so on – to the last and innermost DIV which only spans over star 5. So, when you move your mouse over the fifth star, this triggers the :hoverpseudoclass not only for it, but also for its parent elements (which includes all stars to the left)
HTML markup:
1 2 3 4 5 6 7 8 9 | <div class="stars"> <div class="rating" style="width: 70%;"></div> <div class="star" onclick="event.cancelBubble=true; alert('1 star'); return false;"> <div class="star" onclick="event.cancelBubble=true; alert('2 stars'); return false;"> <div class="star" onclick="event.cancelBubble=true; alert('3 stars'); return false;"> <div class="star" onclick="event.cancelBubble=true; alert('4 stars'); return false;"> <div class="star" onclick="event.cancelBubble=true; alert('5 stars'); return false;"> </div></div></div></div></div> </div> |
CSS:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | .stars { position: relative; float: left; background: url('/images/star_u.png') repeat-x left top; cursor: default; } .stars .star { height: 32px; padding-left: 32px; background: transparent none repeat-x left top; cursor: pointer; } .stars:hover .star { background-image: url('/images/star_u.png'); } .stars:hover .star:hover { background-image: url('/images/star_o.png'); } .stars .rating { position: absolute; height: 100%; background: url('/images/star_d.png') repeat-x left top; } .stars:hover .rating { display: none; } |
Notes:
- You can omit the
.ratingelement if you don’t need it – this will result in all-grey stars when you’re not hovering them. - Instead of specifying explicit dimensions for the container (
.stars), we make itfloat:leftso that its width automatically matches its content. However, this means that we need to take special measures to ensure that it correctly expands the height of its parent element. To do this, you can either append a dummy DIV withclear:bothright after the stars control, or setoverflow:hiddenon the parent element. An alternative approach is to set the width/height of the control explicitly so that you can remove thefloat:leftsetting altogether – whatever suits your environment! If you need the stars only for a read-only indicator for rating, you would just use one.starselement containing the.ratingelement, without all the.starelements – though in this case you’ll have to explicitly define the.starselement’s dimensions instead of floating it. - When handling the onclick event for the
.starelements, don’t forget that they are nested – this means that the event will bubble up the DOM tree through all the parent.starelements, unless YOU prevent it by setting event.cancelBubble to TRUE.
Hope this helps somebody. If you have any questions or suggestions on how to further simplify this or make it more elegant, please feel free to use the comments
