Можно ли отображать только половину значка SVG?

В настоящее время у меня есть рейтинговая система, которая дает 5 звезд, которые могут показывать половинные значения. Я сделал это с помощью Half Stars от FontAwesome и применил несколько трюков CSS, чтобы они выглядели как одна звезда. Но я думал о том, чтобы улучшить свои знания React и CSS, придумав способ отображать только половину значка SVG. Таким образом, вместо использования Half Stars я мог использовать любой значок, который хотел пользователь, и он отображал бы только 50% значка, например, если вы хотели дать оценку 3,5.

В: Можете ли вы показать только половину значка и каким-то образом узнать, нажимает ли пользователь на одну сторону или на другую?

Вот код, который у меня есть в настоящее время, который использует HalfStars для небольшого справки

import React, { useState } from 'react'
import { FaRegStarHalf, FaStarHalf } from 'react-icons/fa'

import './styles/Rater.css'

const Rater = ({ iconCount }) => {
    const [rating, setRating] = useState(null)
    const [hover, setHover] = useState(null)
    // check if user has set a rating by clicking a star and use rating to determine icons
    const Star = rating ? FaStarHalf : FaRegStarHalf

    return (
        <div>
            {[...Array(iconCount), ...Array(iconCount)].map((icon, i) => {
                const value = (i + 1) / 2

                return (
                    <label>
                        <input
                            type='radio'
                            name='rating'
                            value = {value}
                            onClick = {() => {
                                console.info(`value => `, value)
                                return setRating(value)
                            }}
                        />
                        <div className='star-container'>
                            <div>
                                <Star
                                    className = {i % 2 ? 'star-left' : 'star'}
                                    color = {value <= (hover || rating) ? '#ffc107' : '#e4e5e9'}
                                    onMouseEnter = {() => setHover(value)}
                                    onMouseLeave = {() => setHover(null)}
                                />
                            </div>
                        </div>
                    </label>
                )
            })}
        </div>
    )
}

export default Rater


Если вы хотите иметь возможность отображать половину звезды и определять, какая половина была нажата, самый простой способ — показать (половину) звезды в качестве фонового изображения тега <a>. Это означает, что вам также придется установить ширину тега, чтобы отображалась только половина SVG, поэтому вам, возможно, придется изменить свойство тега display на inline-block.

kmoser 19.12.2020 08:39

Этот ответ может быть полезен: stackoverflow.com/questions/29368138/…

Michael Mullany 20.12.2020 19:06

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

cbr 23.12.2020 06:28
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
13
3
2 503
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

Я написал код, чтобы дать вам представление; Если щелкнуть по правой стороне звезды, ее цвет изменится на синий, а если щелкнуть по левой стороне, ее цвет изменится на золотой. Также лучше не использовать stopPropagation и проверять e.target события.

const starIcon = document.getElementById("star");
const icon = document.getElementById("icon");
starIcon.onclick = e => {
  starIcon.style.color = "blue";
  e.stopPropagation();
};
icon.onclick = e => {
  starIcon.style.color = "gold";
}
i {
  clip-path: inset(0 0 0 50%);
  color: gold;
}
  <!DOCTYPE html>
  <html lang = "en">

    <head>
      <meta charset = "UTF-8">
      <title>Document</title>
      <link href = "https://pro.fontawesome.com/releases/v5.10.0/css/all.css" rel = "stylesheet">
    </head>

    <body>
      <span id = "icon"><i id = "star", class = "fas fa-star"></i></span>
    </body>

  </html>

Вот другая идея с использованием 3D-преобразования:

i {
  color: gold;
}

.container {
  background: #fff;
  transform-style: preserve-3d;
}

.half {
  transform: rotateY(1deg);
}

i.fa-star:hover {
  color:red;
}
<link href = "https://pro.fontawesome.com/releases/v5.10.0/css/all.css" rel = "stylesheet">

<div class = "container">
  <i id = "star" class = "fas fa-star fa-5x"></i>
  <i id = "star" class = "fas fa-star fa-5x"></i>
  <i id = "star" class = "fas fa-star fa-5x"></i>
  <i id = "star" class = "fas fa-star fa-5x half"></i>
</div>

<div class = "container">
  <i id = "star" class = "fas fa-star fa-5x"></i>
  <i id = "star" class = "fas fa-star fa-5x half"></i>
</div>

Вы можете сделать это в ОДНОМ SVG

  • Я отказался от иконки Font-Awesome
  • искал "звездочку" среди 7000+ иконок на https://iconmeister.github.io/ (первая загрузка занимает минуту)
  • Выбрал значок звезды с лучшим d-путем (Clarity Iconset: cl-social-star-solid)
  • скопировал только d-путь
  • Отредактировал d-path в https://yqnn.github.io/svg-path-editor/ на 100x100 viewBox/сетка
  • сделал его обратной звездой, добавив M0 0h100v100h-100v-100 к пути
  • Создал новый файл SVG в 0 0 300 100 viewBox, чтобы он соответствовал 3 звездам.. см. ниже
  • Добавлен фоновый прямоугольник, устанавливающий рейтинг золотого цвета с помощью width = "50%"
  • Использованы 3 обратные звезды, каждая со смещением по оси x.
  • добавлено 6 прямоугольников, покрывающих все полузвезды
  • установить встроенные события на каждую "полузвезду"
    (клик работает в этом фрагменте SO, но SO добавляет большую задержку)

Доказательство концепции

<svg viewBox = "0 0 300 100" width = "500px">
  <rect id = "rating" width = "50%" fill = "gold" height = "100%" />

  <path id = "star" fill = "green" 
        d = "M0 0h100v100h-100v-100m91 42a6 6 90 00-4-10l-22-1a1 1 90 01-1 
           0l-8-21a6 6 90 00-11 0l-8 21a1 1 90 01-1 1l-22 1a6 6 90 00-4 
           10l18 14a1 1 90 010 1l-6 22a6 6 90 008 6l19-13a1 1 90 011 0l19
           13a6 6 90 006 0a6 6 90 002-6l-6-22a1 1 90 010-1z"/>
    <use href = "#star" x = "100" />
    <use href = "#star" x = "200" />

  <rect id = "c" width = "16.66%" height = "100%" fill = "transparent" stroke = "red" 
        onclick = "console.info(this)" />
    <use href = "#c" x = "50" />
    <use href = "#c" x = "100" />
    <use href = "#c" x = "150" />
    <use href = "#c" x = "200" />
    <use href = "#c" x = "250" />
</svg>

Компонент звездного рейтинга <star-rating stars=N >

Вы не хотите создавать весь этот SVG вручную... пара строк JavaScript может создать SVG для любого количества звезд.

Здесь используется стандартный веб-компонент W3C, потому что он работает на этой странице и не такой сложный, как компонент React.

https://developer.mozilla.org/en-US/docs/Web/Web_Components

  • Не используя <use>, просто дублируйте все пути и прямоугольники со смещением x
  • события mouseover устанавливают цвет фона %
  • щелчок показывает индекс нажатой полузвезды (0+)
  • Рейтинг может быть установлен в виде значений или процентов; document.querySelector('[stars = "5"]').rating = "90%" (4,5 звезды)
  • требует дополнительной работы для вашего варианта использования

Все необходимые HTML и JavaScript:

<star-rating stars=5 rating = "3.5"
             bgcolor = "green" nocolor = "grey" color = "gold"></star-rating>
<star-rating stars=7 rating = "50%"
             bgcolor = "rebeccapurple" nocolor = "beige" color = "goldenrod"></star-rating>
<script>
  document.addEventListener("click", (evt) => console.info(evt.target.getAttribute("n")))

  customElements.define("star-rating", class extends HTMLElement {
    set rating( rate ) {
      if (!String(rate).includes("%")) rate = Number(rate) / this.stars * 100 + "%";
      this.querySelector("#rating").setAttribute("width", rate);
    }
    connectedCallback() {
      let { bgcolor, stars, nocolor, color, rating } = this.attributes;
      this.stars = ~~stars.value || 5;
      this.innerHTML = 
        `<svg viewBox = "0 0 ${this.stars*100} 100" style = "cursor:pointer;width:300px">`
      + `<rect width = "100%" height = "100" fill = "${nocolor.value}"/>`
      + `<rect id = "rating"  height = "100" fill = "${color.value}"  />`
        + Array(  this.stars     ).fill()
               .map((i, n) => `<path fill = "${bgcolor.value}" d = "M${ n*100 } 0h102v100h-102v-100m91 42a6 6 90 00-4-10l-22-1a1 1 90 01-1 0l-8-21a6 6 90 00-11 0l-8 21a1 1 90 01-1 1l-22 1a6 6 90 00-4 10l18 14a1 1 90 010 1l-6 22a6 6 90 008 6l19-13a1 1 90 011 0l19 13a6 6 90 006 0a6 6 90 002-6l-6-22a1 1 90 010-1z"/>`)
               .join("")
        + Array(  this.stars * 2 ).fill()
               .map((i, n) => `<rect x = "${ n*50 }" n = "${n}" opacity = "0" width = "50" height = "100"`
                  + ` onclick = "dispatchEvent(new Event('click'))" `
                  + ` onmouseover = "this.closest('star-rating').rating = ${(n+1)/2}"/>`)
              .join("") 
      + "</svg>";

      this.rating = rating.value;
    }
  });
</script>

Примечания:

  • Этот нативный компонент <star-rating> (также называемый настраиваемым элементом, потому что НИКАКОЙ shadowDOM не задействован) не имеет зависимостей ZERO.
    • нет библиотек
    • нет внешнего SVG
  • собственные компоненты не являются самозакрывающимися тегами и должны содержать дефис, поэтому обозначение: <star-rating></star-rating>
  • Изменена звезда на M0 0h102v100h-102v-100 (перекрытие 2 пикселя), чтобы скрыть проблемы с округлением SVG.

Поддерживается во всех платформах... кроме...

React пока не поддерживает этот современный стандарт веб-компонентов W3C.

React набирает всего 71% на https://custom-elements-everywhere.com/

Все остальные фреймворки (Angular, Vue, Svelte) поддерживаются на 100%.

Вам нужно проделать дополнительную работу для обработки собственных элементов DOM и событий в React; но пользовательский элемент не сложен. Он создает SVG; должно быть легко воспроизведено как компонент React.

Самое простое решение, которое я могу придумать, — это иметь ряд значков svg, а затем использовать overflow:hidden, чтобы обрезать выбор пользователя.

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

$(document).ready(function()
{
    $(document).on("mousemove", ".star-container.unvoted", function(e)
    {
        let width = $(this).find(".star").width() / 2;

        e.clientX = Math.ceil(e.clientX /= (width)) * width;

        $(this).find(".inner").css("width", e.clientX + "px");
    });

    $(".star-container.unvoted").click(function(e)
    {
        $(this).removeClass("unvoted");
    }); 
});
.star-container .inner{
overflow: hidden;
width:100%;
height:100%;
}

.star-container .inner .star{
display:inline-block;
width:20px;
height:20px;
background-image:url('https://www.flaticon.com/svg/static/icons/svg/1828/1828884.svg');
background-size:contain;
}

.star-container .inner .inner-2{
width:10000px;
}
    
<script src = "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div class = "star-container unvoted">
        <div class = "inner">
            <div class = "inner-2">
                <div class = "star"></div><div class = "star"></div><div class = "star"></div><div class = "star"></div><div class = "star"></div>
            </div>
        </div>
    </div>

Другие вопросы по теме