是否可以只显示一半的 SVG 图标?
Is it possible to only show half of a SVG icon?
我目前有一个评级系统,可以产生 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
我已经写了一段代码来让你明白这个想法;如果您点击星星的右侧,它的颜色会变成蓝色,如果您点击它的左侧,它的颜色会变成金色。此外,最好不要使用 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 图标
- 在 https://iconmeister.github.io/ 的 7000 多个图标中搜索了“star”(首次加载需要一分钟)
- 选择了具有 最佳 d 路径 的星形图标(Clarity Iconset:
cl-social-star-solid
)
- 只复制了d路径
- 将 https://yqnn.github.io/svg-path-editor/ 中的 d 路径编辑为 100x100
viewBox/grid
- 通过将
M0 0h100v100h-100v-100
添加到路径 使其成为 inverse 星
- 在
0 0 300 100
viewBox 中创建了一个新的 SVG 文件以适应 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.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>
星级评分组件<star-rating stars=N >
您不想手动创建所有这些 SVG...JavaScript 几行就可以创建 SVG,对于任意数量的星星
这里使用了一个W3C标准Web组件,因为它运行在这个页面中,不像React组件那么复杂。
https://developer.mozilla.org/en-US/docs/Web/Web_Components
- 不使用
<use>
,只需复制所有路径和具有 x
偏移量的矩形
- 鼠标悬停事件设置背景%颜色
- click显示被点击的半星索引(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
<star-rating>
组件(也称为 Custom Element 因为 NO影子 DOM 涉及)具有 零依赖性
- 没有图书馆
- 没有外部 SVG
- 原生组件不是自闭合标签,必须包含连字符,因此表示法为:
<star-rating></star-rating>
- 将星标更改为
M0 0h102v100h-102v-100
(2 个像素重叠)以解决 SVG 舍入问题
所有框架都支持...除了...
React 尚不支持这种现代 W3C Web 组件标准。
React 在 https://custom-elements-everywhere.com/
上的得分仅为 71%
所有其他框架(Angular、Vue、Svelte)都有 100% 支持
你必须做一些额外的工作来处理 React 中的原生 DOM 元素和事件;但是自定义元素并不复杂……它创建 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>
我目前有一个评级系统,可以产生 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
我已经写了一段代码来让你明白这个想法;如果您点击星星的右侧,它的颜色会变成蓝色,如果您点击它的左侧,它的颜色会变成金色。此外,最好不要使用 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 图标
- 在 https://iconmeister.github.io/ 的 7000 多个图标中搜索了“star”(首次加载需要一分钟)
- 选择了具有 最佳 d 路径 的星形图标(Clarity Iconset:
cl-social-star-solid
) - 只复制了d路径
- 将 https://yqnn.github.io/svg-path-editor/ 中的 d 路径编辑为 100x100 viewBox/grid
- 通过将
M0 0h100v100h-100v-100
添加到路径 使其成为 inverse 星
- 在
0 0 300 100
viewBox 中创建了一个新的 SVG 文件以适应 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.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>
星级评分组件<star-rating stars=N >
您不想手动创建所有这些 SVG...JavaScript 几行就可以创建 SVG,对于任意数量的星星
这里使用了一个W3C标准Web组件,因为它运行在这个页面中,不像React组件那么复杂。
https://developer.mozilla.org/en-US/docs/Web/Web_Components
- 不使用
<use>
,只需复制所有路径和具有x
偏移量的矩形 - 鼠标悬停事件设置背景%颜色
- click显示被点击的半星索引(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
<star-rating>
组件(也称为 Custom Element 因为 NO影子 DOM 涉及)具有 零依赖性- 没有图书馆
- 没有外部 SVG
- 原生组件不是自闭合标签,必须包含连字符,因此表示法为:
<star-rating></star-rating>
- 将星标更改为
M0 0h102v100h-102v-100
(2 个像素重叠)以解决 SVG 舍入问题
所有框架都支持...除了...
React 尚不支持这种现代 W3C Web 组件标准。
React 在 https://custom-elements-everywhere.com/
上的得分仅为 71%所有其他框架(Angular、Vue、Svelte)都有 100% 支持
你必须做一些额外的工作来处理 React 中的原生 DOM 元素和事件;但是自定义元素并不复杂……它创建 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>