5

I am building a little star CSS rating via some radio buttons and have it working ok. I am having an issue being able to fill in the stars before the radio selected, so if star 3 is selected, star 1 and 2 would also be filled in. Here is what I have so far:

#review-form input,
#review-form textarea {
  background: none repeat scroll 0 0 #333333;
  border: 0 none;
  color: #fff;
  margin: 0;
  max-width: 100%;
  padding: 7px 4px;
}
#review-form .radio {
  display: inline-block;
}
#review-form input[type=radio] {
  display: none;
}
#review-form input[type=radio] + label {
  display: block;
}
#review-form input[type='radio'] + label:before {
  display: inline-block;
  font-family: FontAwesome;
  font-style: normal;
  font-weight: normal;
  line-height: 1;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  padding-right: 8px;
  width: 23px;
}
#review-form input[type=radio] + label:before {
  content: "\f006";  /* Radio Unchecked */
}
#review-form input[type=radio]:checked + label:before {
  content: "\f005";  /* Radio Checked */
}
<link href="//netdna.bootstrapcdn.com/font-awesome/3.0/css/font-awesome.css" rel="stylesheet" />
<div class="form-group" id="review-form">
  <label for="rating">RATING</label>
  <span class="star-rating star-5">
                  <div class="radio">
                    <input type="radio" id="option1" name="star-radios" value="1">
                    <label for="option1"></label>
                  </div>
                  <div class="radio">
                    <input type="radio" id="option2" name="star-radios" value="2">
                    <label for="option2"></label>
                  </div>
                  <div class="radio">
                    <input type="radio" id="option3" name="star-radios" value="3">
                    <label for="option3"></label>
                  </div>
                  <div class="radio">
                    <input type="radio" id="option4" name="star-radios" value="4">
                    <label for="option4"></label>
                  </div>
                  <div class="radio">
                    <input type="radio" id="option5" name="star-radios" value="5">
                    <label for="option5"></label>
                  </div>

                </span>

</div>

Codepen


I am wondering if there is a CSS selector (or something) available where I can make the change the stars previous to the select radio button. Trying to avoid using JS if I can.

brasofilo
  • 25,496
  • 15
  • 91
  • 179
ajmajmajma
  • 13,712
  • 24
  • 79
  • 133
  • 3
    This is not what radio buttons are for. Radio buttons only allow you to select (check) one item from the group of buttons. You probably want to use checkboxes instead. Also, I believe this is what you're looking for (though not the answer you want): http://stackoverflow.com/questions/1817792/is-there-a-previous-sibling-css-selector – TheJim01 Nov 14 '16 at 20:06
  • My first guess was to use checkboxes, however I only want the user to be able to select 1 item at a time, so radio seemed like a better option. I also am only sending the one value out of the form (not everything checked, just the 1), so it seemed to make more sense. I don't actually want them checked, just the stars to be filled in, thanks for the link! @TheJim01 – ajmajmajma Nov 14 '16 at 20:07
  • 1
    There is a specific star-rating using `unicode-bidi: bidi-override` which is sorta beautiful in its simplicity. No radiobuttons or checkboxes, though, so you will have to have some logic in the background for how they should be used (hint, use checkboxes, and just mark them all checked and disabled (the ones before), and display the star using ` – junkfoodjunkie Nov 14 '16 at 20:13
  • @ajmajmajma I built a rating component a while back to help another user, let me adjust it a little bit to your requirements. It requires JS though, jQuery dependant, uses an svg spritesheet and CSS. Are you interested? – Ricky Ruiz Nov 14 '16 at 20:16
  • @junkfoodjunkie awesome, i will look that up. is it fairly compatible? – ajmajmajma Nov 14 '16 at 20:20
  • 1
    It's on the other thread. I think it's fairly compatible, yes - shouldn't be too hard to test, though. – junkfoodjunkie Nov 14 '16 at 20:21
  • 2
    You can probably do it with CSS flexbox and the `order` property. Check the examples here: http://stackoverflow.com/a/36118012/3597276 – Michael Benjamin Nov 14 '16 at 20:27
  • @Ricky_Ruiz thank you very much for the offer, I am trying to avoid js/jquery if i can here, if you have a repo for your project i would happily take a look if I cannot find something that does not require js. thanks! – ajmajmajma Nov 14 '16 at 20:34
  • @Michael_B awesome, this might be exactly what I'm looking for!! – ajmajmajma Nov 14 '16 at 20:37
  • 1
    @ajmajmajma Sadly I do not have a repo, I posted an answer and included as many comments as I felt necessary. Hopefully it can be of help. Cheers. – Ricky Ruiz Nov 14 '16 at 21:20
  • @Michael_B Or just using `flex-direction: row-reverse;`? `:)` Please tell me there are no browser drawbacks. – Ricky Ruiz Nov 14 '16 at 23:33
  • 1
    @Ricky_Ruiz, I know of no browser problems with `row-reverse`. I believe it's as stable as `row`. – Michael Benjamin Nov 14 '16 at 23:44
  • 1
    @Michael_B I wish I could have a tiny Michael_B in my pocket every time I want to consult about flexbox. Thanks! – Ricky Ruiz Nov 14 '16 at 23:46

2 Answers2

4

This solution uses just CSS:

Disposing the radios and labels (HTML) in reverse order (from 5 to 1), we can make use of CSS General sibling combinator A ~ B. This way when a radio is selected all labels after it display the correct star icon (selecting radio 4 displays the filled star icon for 4, 3, 2, 1). Then we can reposition each element to the desired order which is 1 2 3 4 5 either:

  • using transform property in each element
  • using flex-direction: row-reverse; property in the container (we need to also assign display: flex; and justify-content: flex-end; for this work); as Ricky suggests in his answer.

/* Using transform */
.use_transform #option1_label {
  transform: translateX(-80px)
}
.use_transform #option2_label {
  transform: translateX(-40px)
}
.use_transform #option4_label {
  transform: translateX(40px)
}
.use_transform #option5_label {
  transform: translateX(80px)
}

/* Using flex */
.use_flex.star-rating {
    display: flex;
    flex-direction: row-reverse;
    justify-content: flex-end;
}

/* Select all next siblings */
#review-form input:checked~label.label_star:before {
  content: "\f005";
}

/**/
#rating {
  float: left;
  margin-right: 10px;
}
.label_star {
  float: left;
  width: 20px;
}

#review-form input,
#review-form textarea {
  background: none repeat scroll 0 0 #333333;
  border: 0 none;
  color: #fff;
  margin: 0;
  max-width: 100%;
  padding: 7px 4px;
}
#review-form .radio {
  display: inline-block;
}
#review-form input[type=radio] {
  display: none;
}
#review-form input[type=radio] + label {
  display: block;
}
#review-form input[type='radio'] + label:before {
  display: inline-block;
  font-family: FontAwesome;
  font-style: normal;
  font-weight: normal;
  line-height: 1;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  //padding-right: 8px;
  width: 23px;
}
#review-form input[type=radio] + label:before {
  content: "\f006";
  /* Radio Unchecked */
}
#review-form input[type=radio]:checked + label:before {
  content: "\f005";
  /* Radio Checked */
}
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"/>
<div class="form-group" id="review-form">
  <label id="rating" for="rating">RATING</label>
  <span class="use_flex star-rating star-5">

  <input type="radio" id="option5" name="star-radios" value="5">
  <label class="label_star" id="option5_label" for="option5"></label>

  <input type="radio" id="option4" name="star-radios" value="4">
  <label class="label_star" id="option4_label" for="option4"></label>
  
  <input type="radio" id="option3" name="star-radios" value="3">
  <label class="label_star" id="option3_label" for="option3"></label>
  
  <input type="radio" id="option2" name="star-radios" value="2">
  <label class="label_star" id="option2_label" for="option2"></label>
  
  <input type="radio" id="option1" name="star-radios" value="1">
  <label class="label_star" id="option1_label" for="option1"></label>

</span>
</div>
Alvaro
  • 9,247
  • 8
  • 49
  • 76
4

CSS Approach:

With CSS only, you can use flexbox.

  • Use flex-direction: row-reverse;

    row-reverse: Behaves the same as row but the main-start and main-end points are permuted.

  • Use the general sibling selector.

    The ~ combinator separates two selectors and matches the second element only if it is preceded by the first, and both share a common parent.


#review-form .star-rating {
  display: inline-flex;
  flex-direction: row-reverse;
}

#review-form input[type=radio] + .label_star::before {
  content: "\f006";
  /* Radio Unchecked */
}

#review-form input[type=radio]:checked ~ .label_star::before {
  content: "\f005";
    /* Radio Checked */
}

#review-form .star-rating {
  display: inline-flex;
  flex-direction: row-reverse;
}
#review-form input,
#review-form textarea {
  background: none repeat scroll 0 0 #333333;
  border: 0 none;
  color: #fff;
  margin: 0;
  max-width: 100%;
  padding: 7px 4px;
}
#review-form .radio {
  display: inline-block;
}
#review-form input[type=radio] {
  display: none;
}
#review-form input[type=radio] + label {
  display: block;
}
#review-form input[type='radio'] + .label_star:before {
  display: inline-block;
  font-family: FontAwesome;
  font-style: normal;
  font-weight: normal;
  line-height: 1;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  //padding-right: 8px;
  width: 23px;
}
#review-form input[type=radio] + .label_star::before {
  content: "\f006";
  /* Radio Unchecked */
}
#review-form input[type=radio]:checked ~ .label_star::before {
  content: "\f005";
  /* Radio Checked */
}
<link href="//netdna.bootstrapcdn.com/font-awesome/3.0/css/font-awesome.css" rel="stylesheet" />
<div class="form-group" id="review-form">
  <label id="rating" for="rating">RATING</label>
  <span class="star-rating star-5">
  <input type="radio" id="option5" name="star-radios" value="5">
  <label class="label_star" id="option5_label" for="option5"></label>
  <input type="radio" id="option4" name="star-radios" value="4">
  <label class="label_star" id="option4_label" for="option4"></label>
  <input type="radio" id="option3" name="star-radios" value="3">
  <label class="label_star" id="option3_label" for="option3"></label>
  <input type="radio" id="option2" name="star-radios" value="2">
  <label class="label_star" id="option2_label" for="option2"></label>
  <input type="radio" id="option1" name="star-radios" value="1">
  <label class="label_star" id="option1_label" for="option1"></label>
</span>
</div>

Revised Codepen


JS Approach:

As said in the comments, I created a small rating component a while back that might help. Some adjusting was done to meet the requirements, it uses JS though.

The component uses the following:

  • SVG Icons
  • SVG Spritesheet
  • HTML5
  • Javascript
  • jQuery
  • CSS3

Simple Rating Component

jQuery Dependant (not complex, could be removed).

//Summary: Star Rating Component, get and/or set a rating displaying icons.
(function() {
// Assign variables for jQuery Objects and classes as string.
  var $rating = $(".rating"),
    sRatingClass = "rated",
    sIcon = "icon-star-full",
    $icon = $("." + sIcon);

// Iterate over jQuery Rating objects.
  $.each($rating, function() {
  // Get the rating if set, you can set this with a REST service and get the current rating.
    var iRating = $(this).data("rating");
    // Filter the icons with a :nth-child pseudo-class and add the 'active' class.
    $(this).find("."+ sIcon + ":nth-child(-n+" + iRating + ")").addClass(sRatingClass);
  });

// Add click event listener to jQuery Icon Objects.
  $icon.on("click", function() {
  // Get the index of the icon being clicked.
    var iIconIndex = $(this).index() + 1,
    // Select the parent of the icon being clicked.
      $iconParent = $(this).parent();
      // Remove the 'active' class to all the icons before setting the new ones.
    $iconParent.find("." + sIcon).removeClass(sRatingClass);
    // Add the 'active' class to the icon clicked and its previous siblings.
    $iconParent.find("." + sIcon + ":nth-child(-n+" + iIconIndex + ")").addClass(sRatingClass);
    // Set the rating to the parent, this can be sent to the backend with a REST service.
    $iconParent.attr("data-rating", iIconIndex);
  });

})();
.svg-spritesheet { /* Hide the SVG Spritesheet to prevent some browsers to render an empty white space. */
  display: none;
}
.icon { /* Default icon properties, using the currentColor variable to be able to change the icon's color with the CSS color property. */
  display: inline-block;
  width: 1em;
  height: 1em;
  stroke-width: 0;
  stroke: currentColor;
  fill: currentColor;
  cursor: pointer;
}
.icon-star-full { /* The icon's fill color */
  color: #C1C2C4;
}
.icon-star-full.rated { /* The icon's 'active' fill color */
  color: #F6DB08;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<section class="rating" data-rating="4">
  <svg class="icon icon-star-full">
    <use xlink:href="#icon-star-full"></use>
  </svg>
  <svg class="icon icon-star-full">
    <use xlink:href="#icon-star-full"></use>
  </svg>
  <svg class="icon icon-star-full">
    <use xlink:href="#icon-star-full"></use>
  </svg>
  <svg class="icon icon-star-full">
    <use xlink:href="#icon-star-full"></use>
  </svg>
  <svg class="icon icon-star-full">
    <use xlink:href="#icon-star-full"></use>
  </svg>
</section>

<section class="rating" data-rating="0">
  <svg class="icon icon-star-full">
    <use xlink:href="#icon-star-full"></use>
  </svg>
  <svg class="icon icon-star-full">
    <use xlink:href="#icon-star-full"></use>
  </svg>
  <svg class="icon icon-star-full">
    <use xlink:href="#icon-star-full"></use>
  </svg>
  <svg class="icon icon-star-full">
    <use xlink:href="#icon-star-full"></use>
  </svg>
  <svg class="icon icon-star-full">
    <use xlink:href="#icon-star-full"></use>
  </svg>
</section>



<svg class="svg-spritesheet">
  <symbol id="icon-star-full" viewBox="0 0 32 32">
    <title>star-full</title>
    <path d="M32 12.408l-11.056-1.607-4.944-10.018-4.944 10.018-11.056 1.607 8 7.798-1.889 11.011 9.889-5.199 9.889 5.199-1.889-11.011 8-7.798z"></path>
  </symbol>
</svg>

jsFiddle


Using IcoMoon free star icon.


Playground:

You could throw in some animation for a little bit of visual improvement, just like AnimateCss does.

$.fn.extend({
    animateCss: function (animationName) {
        var animationEnd = 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend';
        this.addClass('animated ' + animationName).one(animationEnd, function() {
            $(this).removeClass('animated ' + animationName);
        });
    }
});

//Summary: Star Rating Component, get and/or set a rating displaying icons.
(function() {
  // Assign variables for jQuery Objects and classes as string.
  var $rating = $(".rating"),
    sRatingClass = "rated",
    sIcon = "icon-star-full",
    $icon = $("." + sIcon);

  // Iterate over jQuery Rating objects.
  $.each($rating, function() {
    // Get the rating if set, you can set this with a REST service and get the current rating.
    var iRating = $(this).data("rating");
    // Filter the icons with a :nth-child pseudo-class and add the 'active' class.
    $(this).find("." + sIcon + ":nth-child(-n+" + iRating + ")").addClass(sRatingClass);
  });

  // Add click event listener to jQuery Icon Objects.
  $icon.on("click", function() {
   // Animate icon
    $(this).animateCss("bounceIn");
    // Get the index of the icon being clicked.
    var iIconIndex = $(this).index() + 1,
      // Select the parent of the icon being clicked.
      $iconParent = $(this).parent();
    // Remove the 'active' class to all the icons before setting the new ones.
    $iconParent.find("." + sIcon).removeClass(sRatingClass);
    // Add the 'active' class to the icon clicked and its previous siblings.
    $iconParent.find("." + sIcon + ":nth-child(-n+" + iIconIndex + ")").addClass(sRatingClass);
    // Set the rating to the parent, this can be sent to the backend with a REST service.
    $iconParent.attr("data-rating", iIconIndex);
  });

})();
.svg-spritesheet { /* Hide the SVG Spritesheet to prevent some browsers to render an empty white space. */
  display: none;
}
.icon { /* Default icon properties, using the currentColor variable to be able to change the icon's color with the CSS color property. */
  display: inline-block;
  width: 1em;
  height: 1em;
  stroke-width: 0;
  stroke: currentColor;
  fill: currentColor;
  cursor: pointer;
}
.icon-star-full { /* The icon's fill color */
  color: #C1C2C4;
  font-size: 3em;
}
.icon-star-full.rated { /* The icon's 'active' fill color */
  color: #F6DB08;
}

.animated {
    -webkit-animation-duration: 1s;
    animation-duration: 1s;
    -webkit-animation-fill-mode: both;
    animation-fill-mode: both
}
.animated.bounceIn {
    -webkit-animation-duration: .75s;
    animation-duration: .75s
}
@keyframes bounceIn {
    0%,100%,20%,40%,60%,80% {
        -webkit-animation-timing-function: cubic-bezier(0.215,.61,.355,1);
        animation-timing-function: cubic-bezier(0.215,.61,.355,1)
    }

    0% {
        opacity: 0;
        -webkit-transform: scale3d(.3,.3,.3);
        transform: scale3d(.3,.3,.3)
    }

    20% {
        -webkit-transform: scale3d(1.1,1.1,1.1);
        transform: scale3d(1.1,1.1,1.1)
    }

    40% {
        -webkit-transform: scale3d(.9,.9,.9);
        transform: scale3d(.9,.9,.9)
    }

    60% {
        opacity: 1;
        -webkit-transform: scale3d(1.03,1.03,1.03);
        transform: scale3d(1.03,1.03,1.03)
    }

    80% {
        -webkit-transform: scale3d(.97,.97,.97);
        transform: scale3d(.97,.97,.97)
    }

    100% {
        opacity: 1;
        -webkit-transform: scale3d(1,1,1);
        transform: scale3d(1,1,1)
    }
}

.bounceIn {
    -webkit-animation-name: bounceIn;
    animation-name: bounceIn
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<section class="rating" data-rating="4">
  <svg class="icon icon-star-full">
    <use xlink:href="#icon-star-full"></use>
  </svg>
  <svg class="icon icon-star-full">
    <use xlink:href="#icon-star-full"></use>
  </svg>
  <svg class="icon icon-star-full">
    <use xlink:href="#icon-star-full"></use>
  </svg>
  <svg class="icon icon-star-full">
    <use xlink:href="#icon-star-full"></use>
  </svg>
  <svg class="icon icon-star-full">
    <use xlink:href="#icon-star-full"></use>
  </svg>
</section>

<section class="rating" data-rating="0">
  <svg class="icon icon-star-full">
    <use xlink:href="#icon-star-full"></use>
  </svg>
  <svg class="icon icon-star-full">
    <use xlink:href="#icon-star-full"></use>
  </svg>
  <svg class="icon icon-star-full">
    <use xlink:href="#icon-star-full"></use>
  </svg>
  <svg class="icon icon-star-full">
    <use xlink:href="#icon-star-full"></use>
  </svg>
  <svg class="icon icon-star-full">
    <use xlink:href="#icon-star-full"></use>
  </svg>
</section>



<svg class="svg-spritesheet">
  <symbol id="icon-star-full" viewBox="0 0 32 32">
    <title>star-full</title>
    <path d="M32 12.408l-11.056-1.607-4.944-10.018-4.944 10.018-11.056 1.607 8 7.798-1.889 11.011 9.889-5.199 9.889 5.199-1.889-11.011 8-7.798z"></path>
  </symbol>
</svg>
Ricky Ruiz
  • 25,455
  • 6
  • 44
  • 53