Simple Ratings Bar from One Small Image

This time out I'm going to cover something very simple that a LOT of people make needlessly complex. It is also one of the few times I would EVER use the style="" attribute since for the most part style has zero business in the markup. Likewise it's one of the few instances a fixed height makes sense since we're working with images.

Every review site out there typically has some form of visual rating system, like the classic "4 out of 5 stars". Most of these sites waste time making images for every possible rating, or pulling goofy tricks in JavaScript to do something that is actually pretty simple.

How simple, check out this HTML:

<span class="rating"><span style="width:80%"></span> </span>

That's it, that's all you need for markup for one of these rating systems. The CSS isn't exactly rocket science either:

.rating,
.rating span {
	height:16px;
	font-size:1px; /* prevent IE height bug */
	background:url(images/ratingStars.png) 0 0 repeat-x;
}

.rating {
	display:inline-block;
	vertical-align:middle;
	width:80px;
}

.rating span {
	display:block;
	background-position:0 -16px;
}

The image itself is simple too, we're just using CSS sprites:

rating stars

I put up a live demo of that here:
demos/ratings/template.html

As with all my examples the directory...
demos/ratings/template.html
...is wide open for easy access to the gooey bits and pieces.

So how does this work?

The we set both the outer and inner span to the same height, add a font-size to address a minor problem where legacy IE won't let a element be shorter than the font-size. Both also use the same 16x32 image. The top half of the image is the "off" state, the bottom half is the on state. We repeat-x on both. The outer span is 80px wide showing 5 "off" stars (5 * 16 = 80), the inner span is a percentage of that width with the background slid up (as per the incorrectly named CSS sprites) to show that bottom half. Setting the width to whatever percentage you want to show selects the appropriate number of stars for you. 10% is half a star, 20% is one star, 30% is one and a half, right up to that 100% five star limit.

So what's the advantage?

Typically it's no more or less markup than using an img tag, but it results in only needing one file so that's a single handshake, and you can concentrate more on the shape and less on having to make a separate image for each of the possibilities. It's easier to code on the back end and the front end, and easier to change should you want a different shape.

But it's blurry on my mobile!!!

A lot of mobile devices apply a default zoom whether you like it or not. This is due to some really high resolution devices being in circulation where not scaling things up would be illegibly small. Just as in my Scaleable Sprites tutorial we can simply use a double-resolution image and switch our measurements to EM. This will provide a sharp fairly crisp image at any practical size at anything less than 200% enlargement -- given todays bleeding edge retina displays default to 50%, that gives us a LOT of wiggle room for the future without resorting to too large a file.

The markup remains unchanged, but we have to modify the CSS as follows:

.rating,
.rating span {
	height:16px;
	font-size:1px; /* prevent IE height bug */
	background:url(images/ratingStars.png) top left repeat-x;
}

.rating {
	display:inline-block;
	vertical-align:middle;
	width:80px;
}

.rating span {
	display:block;
	background-position:bottom left;
}

@media (min-width:1px) { /* cute trick to target CSS3 browsers only */
	.rating,
	.rating span {
		height:1em;
		font-size:100%;
		background-image:url(images/ratingStarsQuality.png);
		background-size:1em 2em;
	}
	.rating {
		width:5em;
	}
}

Naturally this requires a image at double the resolution to work:

high quality rating stars

Using "top" and "bottom" for the positioning makes it work regardless of scale, we want to go back to font-size:100% so EM's mean what they are supposed to mean, thankfully that hack for legacy IE isn't needed for any versions of IE newer than 7. We then simply switch the background-image for our larger one, and declare the background-size in EM's. .rating of course has to have it's width switched to 5EM to show our five stars.

... and putting most of those changes into a media query means it will gracefully degrade to the pixel sized version in older browsers.

Again, I have provided a live example here.

Summary

This is a very powerful technique that can be leveraged for other effects like animated progress bars using .GIF's, rating systems with more or less rankings, and so forth. It's a very basic thing that I feel most developers should at least have an inkling of how to do BEFORE diving for using multiple images of basically the same thing. It also gracefully degrades to not show anything CSS off, where the width wouldn't make sense anyways which is why I say IF you are going to use a method like this, put a text version next to the blighter!

Projects

  • elementals.js
    A lightweight JavaScript library focusing on cross browser support, ECMAScript polyfills, and DOM manipulation.
  • eFlipper.js
    An image carousel script using elementals.js
  • eProgress.js
    A JavaScript controllable progress bar using elementals.js. Based on the nProgress project that relies on the much heavier jQuery library.

/for_others

Browse code samples of people I've helped on various forums. These code snippets, images, and full rewrites of websites date back a decade or more, and are organized by the forum username of who I was helping. You'll find all sorts of oddball bits and pieces in here. You find any of it useful, go ahead, pick up the ball, and run with it.