solution using SVG

For some websites and platforms on the Internet, it is important to display user ratings as a star rating. I recently had a chance to implement a star rating component for one project with the following requirements:

  • Performance (without using pictures)

  • Adaptability for different sizes

  • Availability

  • Partial star filling (e.g. 3.5 or 3.2)

  • Lightweight CSS support

I decided to use SVG and did not regret it. This article will discuss this implementation method and how it works in different scenarios.

Dear reader, if you are looking for other ways to solve this problem (not just using SVG), I recommend reading the article on the CSS Tricks site “Five Methods for Five-Star Ratings“by Alfred Genkin.

Introduction

Before we start, I would like to demonstrate examples of situations in which this component should work correctly.

The main focus will be on creating a star, for which you can set the fill, outline, and also resize and fill only partially.

Basic markup

First, we need the SVG code for the star image, which we can use in the browser.

<svg width="32" height="32" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
    <path d="M31.547 12a.848.848 0 00-.677-.577l-9.427-1.376-4.224-8.532a.847.847 0 00-1.516 0l-4.218 8.534-9.427 1.355a.847.847 0 00-.467 1.467l6.823 6.664-1.612 9.375a.847.847 0 001.23.893l8.428-4.434 8.432 4.432a.847.847 0 001.229-.894l-1.615-9.373 6.822-6.665a.845.845 0 00.214-.869z" />
</svg>

In the browser, this code will draw a black star with a width and height of 32px.

Availability

Remember that for screen reader users in the attribute aria-label it will be necessary to present the rating not in the form of an image, but in the form of text.

<p aria-label="Rating is 4.5 out of 5">
   <svg width="32" height="32" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
      <path d="M31.547 12a.848.848 0 00-.677-.577l-9.427-1.376-4.224-8.532a.847.847 0 00-1.516 0l-4.218 8.534-9.427 1.355a.847.847 0 00-.467 1.467l6.823 6.664-1.612 9.375a.847.847 0 001.23.893l8.428-4.434 8.432 4.432a.847.847 0 001.229-.894l-1.615-9.373 6.822-6.665a.845.845 0 00.214-.869z" />
   </svg>
</p>

How to reuse SVG

We can either just copy the above markup five times, or save the SVG code for drawing the “path” shape as a template and reuse it without duplicating markup. Let’s do that.

First you need to create an SVG with zero width and height so that it doesn’t take up space

<svg width="0" height="0" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <!-- Content -->
</svg>

In this SVG in the tag <symbol> you need to set the SVG code for drawing the “path” shape, which will draw the star.

According to MDN:

Element symbol is used to define a template of graphic objects, the instances of which can then be repeatedly rendered using the element <use>

In element <symbol>, besides the code that will draw the star icon, it is important to add the attribute idso that you can refer to it later

<svg width="0" height="0" xmlns="http://www.w3.org/2000/svg">
    <symbol xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" id="star">
        <path d="M31.547 12a.848.848 0 00-.677-.577l-9.427-1.376-4.224-8.532a.847.847 0 00-1.516 0l-4.218 8.534-9.427 1.355a.847.847 0 00-.467 1.467l6.823 6.664-1.612 9.375a.847.847 0 001.23.893l8.428-4.434 8.432 4.432a.847.847 0 001.229-.894l-1.615-9.373 6.822-6.665a.845.845 0 00.214-.869z" />
    </symbol>
</svg>

After all these steps, we can reuse the star symbol using the element <use> … The idea is that in the attribute xlink:href to refer to id created symbol.

<p class="c-rate">
    <svg class="c-icon" width="32" height="32">
      <use xlink:href="#star"></use>
    </svg>
    <svg class="c-icon" width="32" height="32">
      <use xlink:href="#star"></use>
    </svg>
    <svg class="c-icon" width="32" height="32">
      <use xlink:href="#star"></use>
    </svg>
    <svg class="c-icon" width="32" height="32">
      <use xlink:href="#star"></use>
    </svg>
    <svg class="c-icon" width="32" height="32">
      <use xlink:href="#star"></use>
    </svg>
</p>

Star styling

Now that we’ve got a list of stars, let’s take a look at CSS styling. I have defined yellow and gray for the star.

<p class="c-rate">
    <svg class="c-icon active" width="32" height="32">
      <use href="#star"></use>
    </svg>
    <svg class="c-icon active" width="32" height="32">
      <use href="#star"></use>
    </svg>
    <svg class="c-icon active" width="32" height="32">
      <use href="#star"></use>
    </svg>
    <svg class="c-icon active" width="32" height="32">
      <use href="#star"></use>
    </svg>
    <svg class="c-icon" width="32" height="32">
      <use href="#star"></use>
    </svg>
</p>
.c-icon {
    --star-active: #fece3c;
    --star-inactive: #6c6962;
    fill: var(--star-inactive);
}

.c-icon.active {
    fill: var(--star-active);
}

The given markup and styles will give the following result

Partial filling

Using SVG gives us two great features. The first is using SVG masks, the second is using SVG gradients.

Half star with SVG mask

The essence of using masks is to use the tag <mask>, specifying the area within which the shape remains visible and outside of which it is cropped.

The figure above demonstrates the application of the described effect, in which only part of the star remains visible.

To do this with SVG, you need to do the following:

  1. Create an SVG template that can be reused

  2. Add item <mask> in the form of a rectangle located on the axis x, and positioned on 50%

  3. Apply a mask to the star shape

<!-- Переиспользуемый SVG-шаблон -->
<svg width="0" height="0" viewBox="0 0 32 32">
  <defs>
    <!-- Маска, которая будет закрывать часть звезды -->
    <mask id="half">
      <rect x="50%" y="0" width="32" height="32" fill="white" />
    </mask>
    <!-- Фигура звезды -->
    <symbol id="star" viewBox="0 0 32 32">
      <path d="M31.547 12a.848.848 0 00-.677-.577l-9.427-1.376-4.224-8.532a.847.847 0 00-1.516 0l-4.218 8.534-9.427 1.355a.847.847 0 00-.467 1.467l6.823 6.664-1.612 9.375a.847.847 0 001.23.893l8.428-4.434 8.432 4.432a.847.847 0 001.229-.894l-1.615-9.373 6.822-6.665a.845.845 0 00.214-.869z" />
    </symbol>
  </defs>
</svg>

<!-- Пример применения звезды с наложением маски -->
<svg class="c-icon" width="32" height="32" viewBox="0 0 32 32">
  <use xlink:href="#star" mask="url(#half)"/>
</svg>

The result will be half a star, as in the previous image.

The next question is how can we use a mask to display a transparent star? Thanks to SVG, we can put in <mask> multiple items.

After adding one more element, the SVG mask <mask> will look like this:

<mask id="half">
  <rect x="0" y="0" width="32" height="32" fill="white" />
  <rect x="50%" y="0" width="32" height="32" fill="black" />
</mask>

In the mask, the white element represents what we want to show, and the black element represents what we want to hide. By combining them, you can create an effect cut out part of the figure

Consider the following image, which visually explains each mask element.

Note that the white rectangle is positioned at the point with coordinates 0, 0, while black is at the point 50%, 0… Visually, it looks like this:

See what happened? The shaded portion represents the final result – half a star. Now you might be thinking about how we can add another star to make the partial selection easier to understand.

By using a brighter color than just black, we get a transparent effect. This means that the right side of the star, which is currently completely hidden, will have a lighter shade of the same color as the star.

<mask id="half">
  <rect x="0" y="0" width="32" height="32" fill="white" />
  <rect x="50%" y="0" width="32" height="32" fill="grey" />
</mask>

Let’s summarize with full markup

<svg width="0" height="0" viewBox="0 0 32 32">
   <defs>
      <mask id="half">
         <rect x="0" y="0" width="32" height="32" fill="white" />
         <rect x="50%" y="0" width="32" height="32" fill="grey" />
      </mask>

      <symbol viewBox="0 0 32 32" id="star">
         <path d="M31.547 12a.848.848 0 00-.677-.577l-9.427-1.376-4.224-8.532a.847.847 0 00-1.516 0l-4.218 8.534-9.427 1.355a.847.847 0 00-.467 1.467l6.823 6.664-1.612 9.375a.847.847 0 001.23.893l8.428-4.434 8.432 4.432a.847.847 0 001.229-.894l-1.615-9.373 6.822-6.665a.845.845 0 00.214-.869z" />
      </symbol>
   </defs>
</svg>

<p class="c-rate">
   <svg class="c-icon" width="32" height="32" viewBox="0 0 32 32">
      <use href="#star" mask="url(#half)" fill="green"></use>
   </svg>
   <!-- Остальные 4 звезды -->
</p>

This gives us a partially filled star. This solution is great in that we don’t need to specify two shades of color. The mask does all the work.

Demonstration

Half star with SVG gradient

Let’s look at the second way to implement partial fill. While looking for a solution, I liked here is this answer on Stackoverflow.

Like a mask, in this case the element <defs> you need to define a gradient.

<svg width="0" height="0" viewBox="0 0 32 32">
  <defs>
    <linearGradient id="half" x1="0" x2="100%" y1="0" y2="0">
      <stop offset="50%" stop-color="#f7efc5"></stop>
      <stop offset="50%" stop-color="#fed94b"></stop>
    </linearGradient>
  </defs>
</svg>

Note that we have two color breakpoints. The first represents the first half of the star, and the second represents the lighter shade. But the disadvantage of this solution is that we need to manually set both colors.

This is how we can use the gradient.

<svg width="0" height="0" viewBox="0 0 32 32">
  <defs>
    <!-- Градиент, который будет задавать два цвета -->
    <linearGradient id="half" x1="0" x2="100%" y1="0" y2="0">
      <stop offset="50%" stop-color="#fed94b"></stop>
      <stop offset="50%" stop-color="#f7efc5"></stop>
    </linearGradient>
    
    <!-- Фигура звезды -->
    <symbol viewBox="0 0 32 32" id="star">
      <path d="M31.547 12a.848.848 0 00-.677-.577l-9.427-1.376-4.224-8.532a.847.847 0 00-1.516 0l-4.218 8.534-9.427 1.355a.847.847 0 00-.467 1.467l6.823 6.664-1.612 9.375a.847.847 0 001.23.893l8.428-4.434 8.432 4.432a.847.847 0 001.229-.894l-1.615-9.373 6.822-6.665a.845.845 0 00.214-.869z" />
    </symbol>
  </defs>
</svg>

<!-- Пример применения звезды с наложением градиента -->
<p class="c-rate">
    <svg class="c-icon" width="32" height="32" viewBox="0 0 32 32">
        <use href="#star" fill="url(#half)"></use>
    </svg>
</p>

Demonstration

Styling an outline

We need a way that will provide for the presence of a contour for the star. It seems nothing complicated. Let’s figure it out.

Outline when using SVG mask

To add an outline, all we need to do is add an SVG element. stroke… This will work well for a full star. However, the partial star will have a clipped outline due to the mask.

To solve this problem, you just need to create another star shape just for the path. We can do this by duplicating the element <use>, but without a mask.

<svg width="0" height="0" viewBox="0 0 32 32">
  <defs>
    <!-- Градиент, который будет задавать два цвета -->
    <linearGradient id="half" x1="0" x2="100%" y1="0" y2="0">
      <stop offset="50%" stop-color="#fed94b"></stop>
      <stop offset="50%" stop-color="#f7efc5"></stop>
    </linearGradient>

    <!-- Фигура звезды -->
    <symbol viewBox="0 0 32 32" id="star">
      <path d="M31.547 12a.848.848 0 00-.677-.577l-9.427-1.376-4.224-8.532a.847.847 0 00-1.516 0l-4.218 8.534-9.427 1.355a.847.847 0 00-.467 1.467l6.823 6.664-1.612 9.375a.847.847 0 001.23.893l8.428-4.434 8.432 4.432a.847.847 0 001.229-.894l-1.615-9.373 6.822-6.665a.845.845 0 00.214-.869z" />
    </symbol>
  </defs>
</svg>

<!-- Пример применения звезды с наложением градиента -->
<p class="c-rate">
    <svg class="c-icon" width="32" height="32" viewBox="0 0 32 32">
        <use href="#star" fill="url(#half)"></use>
        <!-- Дополнительная фигура звезды без заливки, но с контуром -->
        <use href="#star" fill="none" stroke="grey"></use>
    </svg>
</p>

Please note that we now have two elements <use>… One – with a mask that makes the background of half of the star semi-transparent, the other element – only with an outline without applying a mask.

Demonstration

Outline when using SVG gradient

For the gradient method we don’t need to duplicate the icon as there is no mask to crop it. The only thing we need to do is add a stroke.

<svg style="width: 0; height: 0;" viewBox="0 0 32 32">
   <defs>
      <linearGradient id="half" x1="0" x2="100%" y1="0" y2="0">
        <stop offset="50%" stop-color="#f7efc5"></stop>
        <stop offset="50%" stop-color="#fed94b"></stop>
      </linearGradient>
   </defs>
</svg>

<p class="c-rate">
   <svg class="c-icon" width="32" height="32" viewBox="0 0 32 32">
     <use href="#star" fill="url(#half)" stroke="grey"></use>
   </svg>
</p>

Demonstration

The size

Using CSS Variables and making sure the SVG has the correct attribute viewbox, we can easily resize it.

.c-icon {
    width: var(--size, 24px);
    height: var(--size, 24px);
}

.c-icon--md {
    --size: 40px;
}

.c-icon--lg {
    --size: 64px;
}

Hope you enjoyed this article. Thanks for reading.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *