Is it possible to only show half of a SVG icon?
I have written a code to get you the idea; If you click on the right side of the star, its color changes to blue and if you click on the left side, its color changes to gold. Also, it's better to not use stopPropagation
and check e.target
of the event.
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>
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>
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) - 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'))" `
+ ` onmouseover="this.closest('star-rating').rating = ${(n+1)/2}"/>`)
.join("")
+ "</svg>";
this.rating = rating.value;
}
});
</script>
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.
Here is a different idea using 3D transformation:
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>