Is it possible to fill an svg icon partially based on some values? use case : Star Rating
svg{
fill: red;
}
<!DOCTYPE html>
<html lang="en">
<title></title>
<script src="https://unpkg.com/feather-icons"></script>
<body>
<!-- example icon -->
<i data-feather="star"></i>
<i data-feather="star"></i>
<i data-feather="star"></i>
<script>
feather.replace()
</script>
</body>
</html>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
CodePudding user response:
Assuming you can't directly edit the original path, you could manipulate the dom of the SVG to insert a gradient tag with an ID, then reference it in the CSS with fill : url(#gradient);
. It seems a bit hacky but it works :
svg.full {
fill:red;
}
svg#half {
fill : url(#gradient);
}
<!DOCTYPE html>
<html lang="en">
<head>
<title></title>
<script src="https://unpkg.com/feather-icons"></script>
</head>
<body>
<!-- example icon -->
<i class="full" data-feather="star"></i>
<i id="half" data-feather="star"></i>
<i data-feather="star"></i>
<script type="text/javascript">
feather.replace();
// create the defs SVG tag
// it is important to use createElementNS in that case
const defs = document.createElementNS("http://www.w3.org/2000/svg", 'defs');
defs.innerHTML = '<linearGradient id="gradient"><stop offset="50%" stop-color="#F00"></stop><stop offset="50%" stop-color="transparent" ></stop></linearGradient>';
// Here I'm targeting only the middle start by ID
// You should modify the code to target all half-stars
document.getElementById('half').append(defs);
</script>
</body>
</html>
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
PS : By playing with the <stop>
tags attribute's, you could achieve some original effects.
CodePudding user response:
You can do it in ONE SVG
- I ditched the Font-Awesome icon
- searched for "star" in the 7000 icons on https://iconmeister.github.io/ (first load takes a minute)
- Picked the star icon with the best d-path (Clarity Iconset:
cl-social-star-solid
) - copied only the d-path
- Edited the d-path in https://yqnn.github.io/svg-path-editor/ to a 100x100 viewBox/grid
- made it an inverse star by prepending
M0 0h100v100h-100v-100
to the path - Created a new SVG file in a
0 0 300 100
viewBox to fit 3 stars.. see below - Added a background rectangle setting gold color rating with
width="50%"
- Used 3 inverse stars, each at an x-offset
- added 6 rectangles covering all half-stars
- set inline events on every "half-star"
(the click works in this SO snippet, but SO adds a long delay)
Proof of Concept
<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>
<iframe name="sif3" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
A Star Rating Component <star-rating stars=N >
You don't want to create all this SVG by hand... couple lines of JavaScript can create the SVG, for any number of stars
Using a W3C standard Web Component here, because it runs in this page and is not as complex as a React Component.
https://developer.mozilla.org/en-US/docs/Web/Web_Components
- Not using
<use>
, just duplicate all paths and rects with ax
offset - mouseover events set the background % color
- click shows the index of the clicked halfstar (0 )
- Rating can be set with values or percentage;
document.querySelector('[stars="5"]').rating="90%"
(4.5 stars) - might needs extra work for your use case
All required 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'))" `
` onm ouseover="this.closest('star-rating').rating = ${(n 1)/2}"/>`)
.join("")
"</svg>";
this.rating = rating.value;
}
});
</script>
<iframe name="sif4" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
Notes:
- This native
<star-rating>
Component (also called Custom Element because NO shadowDOM is involved) has ZERO dependencies- no libraries
- no external SVG
- native components are not self-closing tags and must contain a hyphen, so notation is:
<star-rating></star-rating>
- Changed the star to
M0 0h102v100h-102v-100
(2 pixel overlap) to cover SVG rounding issues
Supported in all Frameworks... except...
React doesn't support this modern W3C Web Components Standard yet.
React scores just 71% on https://custom-elements-everywhere.com/
All other Frameworks (Angular, Vue, Svelte) have 100% support
You have to do some extra work to handle native DOM Elements and Events in React; but the Custom Element isn't complex.. it creates SVG; should be easy to replicate as a React Component.