【问题标题】:Is it possible to only show half of a SVG icon?是否可以只显示 SVG 图标的一半?
【发布时间】:2021-03-29 15:30:34
【问题描述】:

我目前有一个评分系统,它可以产生 5 颗星,可以显示一半的值。我通过使用 FontAwesome 的半星来做到这一点,并做了一些 CSS 技巧让它们看起来像一颗星。但是我想通过想出一种只显示一半 SVG 图标的方法来增加我的 React 和 CSS 知识。因此,我可以使用用户想要的任何图标,而不是使用半星,并且它只会显示 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.log(`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


【问题讨论】:

  • 如果您希望能够显示半颗星,并检测点击了哪一半,最简单的方法是将(半)星显示为&lt;a&gt; 标签的背景图像。这意味着您还必须设置标签的宽度,以便只显示一半的 SVG,因此您可能需要将标签的 display 属性更改为 inline-block
  • 这个答案可能会有所帮助:stackoverflow.com/questions/29368138/…
  • 您可以使用 CSS clip-path 来屏蔽星星,然后检查点击处理程序是否点击的坐标位于图标的前半部分或后半部分。

标签: javascript css reactjs svg react-icons


【解决方案1】:

我能想到的最简单的解决方案是有一行 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>

【讨论】:

    【解决方案2】:

    您可以在 ONE SVG 中做到这一点

    • 我放弃了 Font-Awesome 图标
    • https://iconmeister.github.io/ 的 7000 多个图标中搜索“星号”(首次加载需要一分钟)
    • 选择具有最佳 d-path 的星形图标(Clarity Iconset:cl-social-star-solid
    • 只复制了 d-path
    • https://yqnn.github.io/svg-path-editor/ 中的 d-path 编辑为 100x100 视图框/网格
    • 通过在路径前添加M0 0h100v100h-100v-100 使其成为
    • 0 0 300 100 viewBox 中创建了一个新的 SVG 文件以适合 3 星。见下文
    • 添加了一个背景矩形设置金色颜色等级width="50%"
    • 使用 3 颗逆星,每颗都在 x 轴上
    • 添加了 6 个矩形 覆盖所有半星
    • 在每个“半星”上设置内联事件
      (点击在这个 SO sn-p 中有效,但 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.log(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>

    星级组件&lt;star-rating stars=N &gt;

    您不想手动创建所有这些 SVG...几行 JavaScript 可以为任意数量的星星创建 SVG

    这里使用W3C标准的Web Component,因为它运行在这个页面中,不像React Component那么复杂。

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

    • 不使用&lt;use&gt;,只需使用x 偏移量复制所有路径和矩形
    • 鼠标悬停事件设置背景 % 颜色
    • 点击显示被点击的半星的索引(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.log(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>

    注意事项

    • 这个native&lt;star-rating&gt;组件(也称为自定义元素,因为不涉及shadowDOM)具有零依赖
      • 没有库
      • 没有外部 SVG
    • 原生组件不是自闭合标签,必须包含连字符,因此表示法为:&lt;star-rating&gt;&lt;/star-rating&gt;
    • 将星号更改为 M0 0h102v100h-102v-100(2 像素重叠)以解决 SVG 舍入问题

    在所有框架中都受支持...除了...

    React 还不支持这种现代 W3C Web 组件标准

    https://custom-elements-everywhere.com/ 上的 React 分数仅为 71%

    所有其他框架(Angular、Vue、Svelte)都有100%支持

    你必须做一些额外的工作来处理 React 中的原生 DOM 元素和事件;但自定义元素并不复杂.. 它创建 SVG;应该很容易复制为 React 组件。

    【讨论】:

      【解决方案3】:

      这是使用 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>

      【讨论】:

        【解决方案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>

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2022-06-17
          • 1970-01-01
          • 2021-11-21
          • 1970-01-01
          • 1970-01-01
          • 2015-12-22
          • 1970-01-01
          • 2013-10-21
          相关资源
          最近更新 更多