Звёздный рейтинг: решение с использованием SVG

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Для некоторых веб-сайтов и платформ в интернете важно отображать оценку материала пользователями в виде звёздного рейтинга. Недавно мне довелось реализовать для одного проекта компонент звёздного рейтинга со следующими требованиями:

  • Производительность (без использования картинок)

  • Адаптивность под разный размер

  • Доступность

  • Частичное заполнение звёзд (например, 3.5 или 3.2)

  • Легкая поддержка с помощью CSS

Я решил использовать SVG и не пожалел об этом. В данной статье будет рассмотрен данный способ реализации и как он работает в разных сценариях

Дорогой читатель, если ты ищешь другие способы решения данной задачи (не только с помощью SVG), рекомендую прочитать на сайте CSS Tricks статью "Five Methods for Five-Star Ratings" автора Alfred Genkin.

Вступление

Прежде чем мы приступим, я бы хотел продемонстрировать примеры ситуаций, в которых данный компонент должен корректно работать.

Основное внимание будет сосредоточено на создании звезды, для которой можно задать заливку, контур, а также изменить размер и заполнить лишь частично.

Базовая разметка

Во-первых, нам понадобится SVG-код изображения звезды, который можно будет использовать в браузере.

<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>

В браузере данный код отрисует чёрную звезду с шириной и высотой, равными 32px.

Доступность

Помните, что для пользователей скринридеров в атрибуте aria-label нужно будет представить рейтинг не в виде изображения, а в виде текста.

<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>

Как использовать SVG повторно

Мы можем либо просто пять раз скопировать приведённую выше разметку, либо сохранить SVG-код рисования фигуры "path" в виде шаблона и использовать повторно без дублирования разметки. Давайте сделаем это.

Сначала нужно создать SVG с нулевой шириной и высотой, чтобы он не занимал место

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

В этом SVG в теге <symbol> нужно задать SVG-код рисования фигуры "path", который нарисует звезду.

Согласно MDN:

Элемент symbol используется для определения шаблона графических объектов, экземпляры которых потом можно многократно отрисовывать с помощью элемента <use>

В элементе <symbol>, кроме кода, который отрисует иконку звезды, важно добавить атрибут id, чтобы на него можно было сослаться позже

<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>

После всех этих действий мы сможем переиспользовать символ звезды с помощью элемента <use> . Идея заключается в том, чтобы в атрибуте xlink:href ссылаться на id созданного символа.

<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>

Стилизация звезды

Теперь, когда мы получили список звёзд, давайте рассмотрим CSS-стилизацию. Я определил для звезды жёлтый и серый цвета.

<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);
}

Приведённые разметка и стили дадут следующий результат

Частичное заполнение

Использование SVG даёт нам две отличных возможности. Первая — использование SVG-масок, вторая — использование SVG-градиентов.

Половина звезды с помощью SVG-маски

Суть использования масок заключается в использовании тега <mask>, задающего область, внутри которой фигура остаётся видимой, а за пределами — обрезается.

На рисунке выше продемонстрировано применение описанного эффекта, при котором видимой остаётся только часть звезды.

Чтобы реализовать это с помощью SVG, нужно сделать следующее:

  1. Создать SVG-шаблон, который можно повторно использовать

  2. Добавить элемент <mask> в виде прямоугольник, расположенного на оси x, и спозиционированном на 50%

  3. Применить маску к фигуре звезды

<!-- Переиспользуемый 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>

В результате получится половина звезды, как на предыдущем изображении.

Далее встаёт вопрос о том, как мы можем с помощью маски отобразить прозрачную звезду? Благодаря SVG, мы можем поместить в <mask> несколько элементов.

После добавления ещё одного элемента, SVG-маска <mask> будет выглядеть следующим образом:

<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>

В маске белый элемент представляет то, что мы хотим показать, а чёрный — то, что хотим скрыть. Объединив их, можно создать эффект вырезанной части фигуры.

Рассмотрим следующее изображение, на котором визуально объясняется каждый элемент маски.

Обратите внимание, что белый прямоугольник спозиционирован в точке с координатами 0, 0, в то время как чёрный — в точке 50%, 0. Визуально это выглядит следующим образом:

Видите, что произошло? Заштрихованная часть представляет конечный результат — половину звезды. Теперь вы могли подумать о том, как нам добавить ещё одну звезду, чтобы частичное выделение было более понятно.

При использовании более яркого цвета, чем просто чёрный, мы получим эффект прозрачности. Это значит, что правая часть звезды, которая на данный момент полностью скрыта, будет иметь светлый оттенок того же цвета, что и звезда.

<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>

Давайте подведём итог с полной разметкой

<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>

Таким образом, мы получили звезду с частичной заливкой. Данное решение замечательно тем, что нам не нужно указывать два оттенка цвета. Всю работу делает маска.

Демонстрация

Половина звезды с помощью SVG-градиента

Рассмотрим второй способ реализации частичной заливки. Во время поисков решения мне понравился вот этот ответ на Stackoverflow.

Подобно маске, в этом случае в элементе <defs> нужно определить градиент.

<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>

Обратите внимание, что у нас есть две точки останова для цвета. Первая обозначает первую половину звезды, а втора — светлый оттенок. Но недостатком данного решения является то, что нам нужно вручную задавать оба цвета.

Вот как мы можем использовать градиент.

<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>

Демонстрация

Стилизация контура

Нам нужен способ, который будет предусматривать наличие у звезды контура. Кажется, ничего сложного. Давайте разберёмся.

Контур при использовании SVG-маски

Чтобы добавить контур, всё что нам нужно, это добавить SVG-элементу stroke. Это будет хорошо работать для полной звезды. Однако, у частичной звезды контур будет обрезанным из-за маски.

Чтобы решить эту проблему, достаточно просто создать ещё одну фигуру звезды именно для контура. Мы можем сделать это, продублировав элемент <use>, но уже без маски.

<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>

Обратите внимание, что у нас стало два элемента <use>. Один — с маской, делающей фон половины звезды полупрозрачным, другой элемент — только с контуром без применения маски.

Демонстрация

Контур при использовании SVG-градиента

Для способа с использованием градиента нам не нужно дублировать иконку, поскольку нет маски, которая обрезала бы её. Единственное, что нам нужно сделать, так это добавить обводку.

<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>

Демонстрация

Размер

Используя CSS-переменные и убедившись, что в SVG задан правильный атрибут viewbox, мы легко можем изменять его размер.

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

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

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

Надеюсь, вам понравилась данная статья. Благодарю за прочтение.

Источник: https://habr.com/ru/post/588695/


Интересные статьи

Интересные статьи

Рано или поздно, каждый пэхапешник, пишущий на битриксе, начинает задумываться о том, как бы его улучшить, чтобы и всякие стандарты можно было соблюдать, и современные инструменты разработки использов...
Всем привет,Я последние несколько лет очень часто сталкиваюсь с проектами по адаптации под 152-ФЗ и он мне честно порядком надоел. Поэтому, прочитав опять весь закон, все...
Я сделал по этому проекту уже достаточно, чтобы начать говорить о нём, как о готовом к выходу, а не просто как о задумке, движущейся по бесконечной дороге к туманному и неопределё...
Привет всем читателям! Я продолжаю тему программного эмулятора для PlayStation 1, PlayStation 2 и PlayStation Portable — Omega Red. Более подробно: Видео гид по эмулятору Ome...
Я имею кое-какой 8ми летный опыт в ковырянии кода. За это время успел попробовать много разных языков и технологий в разных направлениях: от «разработки» всяких фишинговых приколов на PHP Devel S...