So I wrote a simple piece of JavaScript the other day that showcases a neat star rating effect. In this article I’ll not only showcase this effect, but I’ll also explain some of the code that’s going on in it.
Demo
See the Pen Star rating by Giel Berkers (@kanduvisla) on CodePen.
This is the demo. You can hover over the stars and they will animate when hovered. Nothing too fancy-pancy, but there are some interesting things to notice:
- The stars aren’t a webfont of some kind, but just simple Unicode stars.
- The animations are done with CSS.
- It uses a minimal amount of JavaScript to highlight the stars up to the star that is ‘hovered’.
The HTML
The HTML is pretty simple and pretty straight-forward. It’s just a container with some spans containing a star:
1 2 3 4 5 6 7 |
<div id="rating"> <span>?</span> <span>?</span> <span>?</span> <span>?</span> <span>?</span> </div> |
Nothing interesting here, let’s move to…
The CSS
The CSS is also quite simple, but let’s take a look at the CSS of the rating box and the individual star elements:
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 |
#rating { text-align: center; perspective: 250px; position: absolute; top: 40%; width: 100%; } #rating span { cursor: pointer; font-size: 50px; padding: 0 10px; color: #fff; opacity: .5; transition: all 150ms; display: inline-block; transform: rotateX(45deg); transform-origin: center bottom; } #rating span.hover { color: #ff0; opacity: 1; transform: rotateX(0deg); text-shadow: 0 0 30px #ffc; } |
The first thing you’ll notice in the demo is that the stars have a small, but elegant and subtle 3D transformation. It appears as if they are just lying there on the ground. This is done with the transform: rotateX(45deg) property on the span elements. To set the ‘pivot point’ of these span elements on the bottom, we set the transform-origin to center bottom , and to give the whole thing a 3D experience we set the perspective of the parent element (#rating ) to 250px .
Another thing to note here is the hover -class we defined. This is the style the element gets when it’s hovered on: the color and opacity changes, the rotation is set to 0, which causes it to ‘stand up’, and we misuse the text-shadow to create a glow effect. The transition: all 150ms we defined on the global span element causes our changes to get animated so it looks super-cool!
Why .hover and not :hover?
You might ask yourself the question now:
“why are you defining the hover style with a class instead of just using the :hover pseudo class?”
It’s a good question, and the answer is simple: when we hover over a star, we want all the stars before the star we are hovering on the become active as well. Although this is not impossible with just CSS, it’s not the most cleanest solution to go for. And: at some point you need to bind a click-event to the span elements, so you are going to have to use JavaScript at some point.
The JavaScript
I wrote the JavaScript in an object-oriented approach, since that is the way how I write JavaScript nowadays. Let’s take a look at the code:
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
/** * Star rating class * @constructor */ function StarRating() { this.init(); }; /** * Initialize */ StarRating.prototype.init = function() { this.stars = document.querySelectorAll('#rating span'); for (var i = 0; i < this.stars.length; i++) { this.stars[i].setAttribute('data-count', i); this.stars[i].addEventListener('mouseenter', this.enterStarListener.bind(this)); } document.querySelector('#rating').addEventListener('mouseleave', this.leaveStarListener.bind(this)); }; /** * This method is fired when a user hovers over a single star * @param e */ StarRating.prototype.enterStarListener = function(e) { this.fillStarsUpToElement(e.target); }; /** * This method is fired when the user leaves the #rating element, effectively removing all hover states. */ StarRating.prototype.leaveStarListener = function() { this.fillStarsUpToElement(null); }; /** * Fill the star ratings up to a specific position. * @param el */ StarRating.prototype.fillStarsUpToElement = function(el) { // Remove all hover states: for (var i = 0; i < this.stars.length; i++) { if (el == null || this.stars[i].getAttribute('data-count') > el.getAttribute('data-count')) { this.stars[i].classList.remove('hover'); } else { this.stars[i].classList.add('hover'); } } }; // Run: new StarRating(); |
Let’s take a look at the init() -method. What it does is the following:
- It creates a reference of all the star-elements.
- It adds a count-number and binds a mouseenter -event to each star.
- It adds a mouseleave -event to the outer wrapper.
If you look at the enterStarListener() – and leaveStarListener() you’ll notice that they both execute the same method: fillStarsUpToElement() . This is the method that does all the magic. So let’s just take a look at this method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/** * Fill the star ratings up to a specific position. * @param el */ StarRating.prototype.fillStarsUpToElement = function(el) { // Remove all hover states: for (var i = 0; i < this.stars.length; i++) { if (el == null || this.stars[i].getAttribute('data-count') > el.getAttribute('data-count')) { this.stars[i].classList.remove('hover'); } else { this.stars[i].classList.add('hover'); } } }; |
Well this is the core of the functionality: it iterates through all stars and checks the following:
- If the element provided to this method is null , remove the hover-class of this star.
- If the count-number of this star is higher than the count-number of the element provided to this method also remove the hover-class of this star.
- Of both of the above is not the case, this star should have the hover-class.
It’s actually very simple logic and it provides a solid functionality for this tiny star rating demo. The only thing that’s left to do is add an event listener that listens to clicks (or taps), but that’s one I’m saving for another article.
Visitors give this article an average rating of 3.4 out of 5.
How would you rate this article?
★ ★ ★ ★ ★
Where is the article for the event listener that listens to clicks?