How to use svg for clip path - html

I'm trying to build a star rating feature, and am surprised to find that the part that's giving me the most trouble is figuring out how to get the star shape. It seems to me that the most straightforward approach, given what I have built already, is to try and use an SVG star shape to create a clip path on the div elements that show the rating. I'm familiar with clip-path in a general sense, and have used it for more basic shapes, but am struggling a lot to understand how to use it with an svg and why my current code is not working (svgs are not exactly in my typical wheelhouse).
The code:
const stars = document.getElementsByClassName('star');
function fillStars(starClasses, placeRating) {
const wholeFill = Math.floor(placeRating);
const decimalFill = placeRating % 1;
const yellowFill = decimalFill.toFixed(2).toString().replace('0.', '');
for (let i = 0; i < wholeFill; i++) {
starClasses[i].style.backgroundColor = '#fbff01';
}
if (placeRating - wholeFill !== 0) {
starClasses[starClasses.length - 1].style.background = `linear-gradient(90deg, #fbff01 ${yellowFill}%, #FFFFFF 0%)`;
}
}
fillStars(stars, 4.3);
.star {
width: 30px;
height: 30px;
border: solid 1px gray;
clip-path: url(#star-clip);
}
#rating {
display: flex;
}
<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0">
<defs>
<clipPath id="star-clip" clipPathUnits="objectBoundingBox">
<rect fill="#fff" id="canvas_background" height="40" width="41" y="-1" x="-1"/>
<rect fill="url(#gridpattern)" stroke-width="0" y="0" x="0" height="100%" width="100%"/>
<path id="svg_1" d="m3.885,14.64105l11.84103,0l3.65897,-12.22295l3.65897,12.22295l11.84103,0l-9.57958,7.55411l3.65916,12.22295l-9.57958,-7.55431l-9.57958,7.55431l3.65916,-12.22295l-9.57958,-7.55411z" stroke-width="1.5" stroke="#000" fill="#fff"/>
</clipPath>
</defs>
</svg>
<div id="header">
<div id="rating">
<div id="star-1" class="star"></div>
<div id="star-2" class="star"></div>
<div id="star-3" class="star"></div>
<div id="star-4" class="star"></div>
<div id="star-5" class="star"></div>
</div>
</div>

There's a good answer here about calculating a clipPath's 0-1 range from the original SVGs dimensions.
.star {
width: 30px;
height: 30px;
clip-path: url(#star-clip);
background: Gold;
}
#rating {
display: flex;
}
<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0">
<defs>
<clipPath id="star-clip" clipPathUnits="objectBoundingBox" transform="scale(0.025, 0.02439)">
<path d="m3.885,14.64105l11.84103,0l3.65897,-12.22295l3.65897,12.22295l11.84103,0l-9.57958,7.55411l3.65916,12.22295l-9.57958,-7.55431l-9.57958,7.55431l3.65916,-12.22295l-9.57958,-7.55411z" />
</clipPath>
</defs>
</svg>
<div id="rating">
<div id="star-1" class="star"></div>
<div id="star-2" class="star"></div>
<div id="star-3" class="star"></div>
<div id="star-4" class="star"></div>
<div id="star-5" class="star"></div>
</div>

Can you use clip-path: polygon(); ?
I think url(#id) is not fully supported or hard to get correct.
A not about the border: the border will be applied to the bounding box
see the last star
.star {
width: 50px;
height: 50px;
background-color: pink;
border: solid 1px gray;
clip-path: polygon(54.5% 24.5%, 80.25% 24.27%, 60.73% 34.91%, 75% 52.25%, 44.5% 41.24%, 15% 52.75%, 30.00% 34.75%, 1.5% 25.25%, 31.77% 24.86%, 41.82% 9.01%);
}
.star-last {
background-color: #f1e745;
height: 50px;
width: 50px;
clip-path: polygon(54.5% 24.5%, 80.25% 24.27%, 60.73% 34.91%, 75% 52.25%, 44.5% 41.24%, 15% 52.75%, 30% 34.75%, -200.42% 25.25%, 31.77% 24.86%, 41.82% 9.01%);
border: 1px solid black;
}
#rating {
display: flex;
}
<div id="header">
<div id="rating">
<div id="star-1" class="star"></div>
<div id="star-2" class="star"></div>
<div id="star-3" class="star"></div>
<div id="star-4" class="star"></div>
<div id="star-5" class="star-last"></div>
</div>
</div>

Edit: As I stated it is hard to get right with a standard SVG path, however adding the transform scale is what you need to do as pointed out in the chosen answer, something I now learnt. Below are other way to achieve the same ends but the chosen answer is the best way.
Maybe it is your path definition, as I said in my other answer url(#id) seems somewhat difficult to get right with standard SVG paths.
I copied a heart path from MDN and removed the rects and your code works.
Edit: If you can't work out correct definition syntax for a star path it is possible to use the polygon() code from CSS as decimal in place of the path in the svg.
remove <path> from svg and replace with.
<polygon points="0.545 0.245, 0.8025 0.2427, 0.6073 0.3491, 0.75 0.5225, 0.445 0.4124, 0.15 0.5275, 0.3000 0.3475, 0.15 0.2525, 0.3177 0.2486, 0.4182 0.0901" />
const stars = document.getElementsByClassName('star');
function fillStars(starClasses, placeRating) {
const wholeFill = Math.floor(placeRating);
const decimalFill = placeRating % 1;
const yellowFill = decimalFill.toFixed(2).toString().replace('0.', '');
for (let i = 0; i < wholeFill; i++) {
starClasses[i].style.backgroundColor = '#fbff01';
}
if (placeRating - wholeFill !== 0) {
starClasses[starClasses.length - 1].style.background = `linear-gradient(90deg, #fbff01 ${yellowFill}%, #FFFFFF 0%)`;
}
}
fillStars(stars, 4.3);
.star {
width: 30px;
height: 30px;
border: solid 1px gray;
clip-path: url(#star-clip);
}
#rating {
display: flex;
}
<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0">
<defs>
<clipPath id="star-clip" clipPathUnits="objectBoundingBox">
<path d="M0.5,1
C 0.5,1,0,0.7,0,0.3
A 0.25,0.25,1,1,1,0.5,0.3
A 0.25,0.25,1,1,1,1,0.3
C 1,0.7,0.5,1,0.5,1 Z" ></path>
</clipPath>
</defs>
</svg>
<div id="header">
<div id="rating">
<div id="star-1" class="star"></div>
<div id="star-2" class="star"></div>
<div id="star-3" class="star"></div>
<div id="star-4" class="star"></div>
<div id="star-5" class="star"></div>
</div>
</div>

Related

Flex with gap fill space in last row

When I use flex with gap, the last row has some space at the end (I think equal in size to gap): https://jsfiddle.net/Lgnzj4ya/
Why doesn't the last row take up all the space like the first one? It does if I remove the gap, but I want the gap between items. My understanding is align-items: stretch (the default) should fill all the space.
Interestingly, I have this problem in JSFiddle and on my website, but not in the Stackoverflow code snippet:
.flex-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 20px;
}
.flex-item {
flex: 1;
}
<div class="flex-container">
<input class="flex-item" type="text" placeholder="First name">
<input class="flex-item" type="text" placeholder="Last name">
<input class="flex-item" type="text" placeholder="Address">
<input class="flex-item" type="text" placeholder="Email">
<input class="flex-item" type="text" placeholder="Phone">
</div>
Image of problem (there is a space after the "Phone" input):
It seems that Lastpass password vault app uses the exact same input placeholder values to trigger placement of its input auto-fill options icon.
It's an SVG and it happens to be max-width: 22px and max-height: 18px
Inserting this in your CSS seems to solve the issue, it will disable the icon, but not the underlying Lastpass functionality:
div[data-lastpass-icon-root] { display: none }
A rather sloppy development solution by Lastpass, as it should in no way have to interfere with the regular flow of a client HTML document.
For the interested, the injected HTML:
<div data-lastpass-icon-root="true" style="position: relative !important; height: 0px !important; width: 0px !important; float: left !important;"></div>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" data-lastpass-icon="true" style="position: absolute; cursor: pointer; height: 18px; max-height: 18px; width: 22px; max-width: 22px; top: -40px; left: -443.667px; z-index: auto; color: rgb(215, 64, 58);"><rect x="0.680176" y="0.763062" width="22.6392" height="22.4737" rx="4" fill="currentColor"></rect><path fill-rule="evenodd" clip-rule="evenodd" d="M19.7935 7.9516C19.7935 7.64414 20.0427 7.3949 20.3502 7.3949C20.6576 7.3949 20.9069 7.64414 20.9069 7.9516V16.0487C20.9069 16.3562 20.6576 16.6054 20.3502 16.6054C20.0427 16.6054 19.7935 16.3562 19.7935 16.0487V7.9516Z" fill="white"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M4.76288 13.6577C5.68525 13.6577 6.43298 12.9154 6.43298 11.9998C6.43298 11.0842 5.68525 10.3419 4.76288 10.3419C3.8405 10.3419 3.09277 11.0842 3.09277 11.9998C3.09277 12.9154 3.8405 13.6577 4.76288 13.6577Z" fill="white"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M10.3298 13.6577C11.2521 13.6577 11.9999 12.9154 11.9999 11.9998C11.9999 11.0842 11.2521 10.3419 10.3298 10.3419C9.4074 10.3419 8.65967 11.0842 8.65967 11.9998C8.65967 12.9154 9.4074 13.6577 10.3298 13.6577Z" fill="white"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M15.8964 13.6577C16.8188 13.6577 17.5665 12.9154 17.5665 11.9998C17.5665 11.0842 16.8188 10.3419 15.8964 10.3419C14.974 10.3419 14.2263 11.0842 14.2263 11.9998C14.2263 12.9154 14.974 13.6577 15.8964 13.6577Z" fill="white"></path></svg>

I would like to put an image inside svg shape and hide the overflow parts

.container{
position: relative;
width: 692px;
}
.image{
position: absolute;
width: 403px;
height: 602px;
top: 91px;
left: 92px;
}
<html>
<body>
<div class="container">
<svg width="581" height="692" viewBox="0 0 581 692" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M526.636 489.523C598.467 361.569 590.666 251.284 551.879 107.757C538.582 58.5517 506.556 -37.4658 444.069 -103.204C320.276 -233.438 122.218 -189.737 6.51981 -180.688C-109.178 -171.639 -138.103 -67.5724 -164.924 3.12491C-191.745 73.8223 -123.378 416.563 -84.461 503.097L-84.2626 503.538C-45.3885 589.978 0.49324 692 167.445 692C334.682 692 444.781 635.333 526.636 489.523Z"
fill="green"/>
</svg>
<div class="image">
<img src="https://dummyimage.com/403x602/000/efefef" width={403} height={602}/>
</div>
</div>
</body>
</html>
Please don't mind the white background of image.
As you can see, I would like to remove the bottom-right overflow parts. The green area is the svg. I tried with z-index and svg clip-path. Unfortunately those methods didn't work for me somehow. Help is much appreciated. Thanks.
As I've told you in the comment: you can put the image inside the svg and clip the image with the path.
<svg width="581" height="692" viewBox="0 0 581 692" fill="none" xmlns="http://www.w3.org/2000/svg">
<path id="thePath" d="M526.636 489.523C598.467 361.569 590.666 251.284 551.879 107.757C538.582 58.5517 506.556 -37.4658 444.069 -103.204C320.276 -233.438 122.218 -189.737 6.51981 -180.688C-109.178 -171.639 -138.103 -67.5724 -164.924 3.12491C-191.745 73.8223 -123.378 416.563 -84.461 503.097L-84.2626 503.538C-45.3885 589.978 0.49324 692 167.445 692C334.682 692 444.781 635.333 526.636 489.523Z" fill="green" />
<clipPath id="cp">
<use href="#thePath" />
</clipPath>
<image clip-path="url(#cp)" href="https://dummyimage.com/403x602/000/efefef" width="403" x="100" y="100" />
</svg>

Cropped SVG with Clip-path changes Height on zooming in and out [duplicate]

I wanted a cylindrical container containing liquid and this liquid changes color and its amount in that container, So I used SVG for this purpose (SVG is used for liquid in a cylindrical container).
Here is the source code along with SVG
function changeCol(col) {
document.querySelector('path').style.setProperty('fill', col, '!important');
document.querySelector('ellipse').style.setProperty('fill', col, '!important');
//Its not working I dont know why.
}
function changeHeight(vol) {
//Some Code to change its height.
}
.container {
width: fit-content;
border: solid red;
}
.liquid {
width: 200px;
}
.liquid svg * {
fill: red !important;
}
<div class="container">
<div class="liquid">
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 216.66 325.25">
<title>liquid_RBt</title>
<path d="M216.66,28V297.25c0,15.47-48.5,28-108.33,28S0,312.72,0,297.25V28C0,43.46,48.5,56,108.33,56S216.66,43.46,216.66,28Z" style="fill:#fff;stroke:#fff;stroke-miterlimit:10;opacity:0.7000000000000001"/>
<ellipse cx="108.33" cy="28" rx="108.33" ry="28" style="fill:#fff;stroke:#fff;stroke-miterlimit:10;opacity:0.8"/>
</svg>
</div>
</div>
<div class="controller">
<div class="color-change">
<button class="col-btn" onclick="changeCol('red');">Red</button>
<button class="col-btn" onclick="changeCol('blue');">Blue</button>
<button class="col-btn" onclick="changeCol('green');">green</button>
</div>
<div class="change-amount">
<input type="number" id="amountInp" onchange='changeHeight(this.value)' placeholder="(in ml)">
</div>
</div>
I've clipped the path with a clipPath that I then move about to hide the parts of the polygon that need to disappear.
I don't know the volume of the cylinder either so you might want to scale the number.
I also fixed the colouring.
function changeCol(col) {
document.querySelector('path').style.setProperty('fill', col);
document.querySelector('ellipse').style.setProperty('fill', col);
}
function changeHeight(vol) {
// not sure how much 100ml is supposed to fill up
document.querySelector('ellipse').cy.baseVal.value = 300 - vol;
document.querySelector('rect').y.baseVal.value = 300-vol;
}
.container {
width: fit-content;
border: solid red;
}
.liquid {
width: 200px;
}
<div class="container">
<div class="liquid">
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 216.66 325.25">
<title>liquid_RBt</title>
<defs>
<clipPath id="cp">
<rect x="0" y="0" width="230" height="400"/>
</clipPath>
</defs>
<path d="M216.66,28V297.25c0,15.47-48.5,28-108.33,28S0,312.72,0,297.25V28C0,43.46,48.5,56,108.33,56S216.66,43.46,216.66,28Z" style="fill:red;stroke:#fff;stroke-miterlimit:10;opacity:0.7000000000000001;clip-path: url(#cp)"/>
<ellipse cx="108.33" cy="28" rx="108.33" ry="28" style="fill:red;stroke:#fff;stroke-miterlimit:10"/>
</svg>
</div>
</div>
<div class="controller">
<div class="color-change">
<button class="col-btn" onclick="changeCol('red');">Red</button>
<button class="col-btn" onclick="changeCol('blue');">Blue</button>
<button class="col-btn" onclick="changeCol('green');">green</button>
</div>
<div class="change-amount">
<input type="number" id="amountInp" onchange='changeHeight(this.value)' placeholder="(in ml)">
</div>
</div>
Set the preserve aspect ratio to none.
<svg preserveAspectRatio="none" /* here are your other attributes */>

How can i make Internet Explorer understand my SVG css

Im in the process of generating a map through html and css, however i seem to have an issue when it comes to Internet Explorer as the map is not shown at all i just get a blank page.
the following code is the html part of my map.
<div class="svg">
<svg class="box" width="65%" height="auto" viewbox="0 0 800 800"
xmlns="http://www.w3.org/2000/svg">
<a href="https://www.google.dk"><polygon class="haderslev"
points="519,5, 538,46, 556,65, 576,61, 591,39, 609,43, 615,60, 622,73,
635,77, 661,70, 730,32, 744,49, 741,69, 731,90, 742,104, 781,128, 787,142,
784,154, 772,179, 769,197, 750,207, 683,212, 667,236, 660,276, 658,281,
626,270, 607,254, 563,247, 520,233, 513,244, 509,265, 371,364, 352,252,
456,227, 437,218, 426,221, 409,242, 389,238, 384,229, 391,216, 400,201,
393,185, 373,170, 349,169, 318,182, 315,197, 302,202, 297,196, 294,171,
290,157, 295,124, 279,99, 265,60, 271,56, 296,63, 311,69, 316,58, 319,49,
351,52, 381,70, 398,50, 406,60, 409,94, 433,98, 451,89, 448,65, 452,50,
463,46, 479,42, 488,12, 507,2" />
<foreignobject x="490" y="125" width="140px" height="30px"
class="haderslevnode">
<div class="htext">Haderslev</div>
</foreignobject></a>
</svg>
</div>
And this is the css to make it work. which functions perfectly on Chrome and Firefox respectively.
.svg{
height: 100vmin;
}
.haderslev{
fill:#7c7c7c;
}
.haderslev:hover{
fill:#eab616;
}
.htext{
font-family:"Helvetica Neue",Helvetica,Arial,sans-serif,MagistralBold;
font-weight:bold;
font-size:24px;
color:#ffffff;
}
any advice is apreciated as i would like this to be as accessible as posible.
*Edit
Internet explorer 10 and 11 are the versions i would like this to funktion in
This is working for me in IE:
I've removed the foreign object and I'm using a text element instead. The class .htext has a fill instead of a color.
I've removed width="65%" height="auto" attributes of the SVG element and I've added this in css:
.svg{
height: 100vmin;
width: 100vmin;
}
svg{border:1px black}
.svg{
height: 100vmin;
width: 100vmin;
border:1px solid;
}
.haderslev{
fill:#7c7c7c;
}
.haderslev:hover{
fill:#eab616;
}
.htext{
font-family:"Helvetica Neue",Helvetica,Arial,sans-serif,MagistralBold;
font-weight:bold;
font-size:30px;
fill:#ffffff;
}
<div class="svg">
<svg class="box" viewbox="0 0 800 800"
xmlns="http://www.w3.org/2000/svg">
<a href="https://www.google.dk"><polygon class="haderslev"
points="519,5, 538,46, 556,65, 576,61, 591,39, 609,43, 615,60, 622,73,
635,77, 661,70, 730,32, 744,49, 741,69, 731,90, 742,104, 781,128, 787,142,
784,154, 772,179, 769,197, 750,207, 683,212, 667,236, 660,276, 658,281,
626,270, 607,254, 563,247, 520,233, 513,244, 509,265, 371,364, 352,252,
456,227, 437,218, 426,221, 409,242, 389,238, 384,229, 391,216, 400,201,
393,185, 373,170, 349,169, 318,182, 315,197, 302,202, 297,196, 294,171,
290,157, 295,124, 279,99, 265,60, 271,56, 296,63, 311,69, 316,58, 319,49,
351,52, 381,70, 398,50, 406,60, 409,94, 433,98, 451,89, 448,65, 452,50,
463,46, 479,42, 488,12, 507,2" />
<text class="htext" x="490" y="150">Haderslev</text>
</a>
</svg>
</div>
I hope it helps.

How to create a clickable grid of triangles using html, svg?

I have already created a grid of triangles like so:
svg {
margin-left: 0px;
margin-right: -60px;
padding: 0;
}
<div data-bind="foreach: Grid">
<div data-bind="foreach: $data.rowData">
<!-- ko if: $data.owner() === 0 && ($data.pos[0] + $data.pos[1])%2 === 0-->
<svg height="103.92" width="120">
<polygon class="" points="60,0 0,103.92 120,103.92" style="fill:grey;" data-bind="click: $root.test.bind($data, $data)" />
</svg>
<!-- /ko -->
<!-- ko if: $data.owner() === 0 && ($data.pos[0] + $data.pos[1])%2 === 1-->
<svg height="103.92" width="120">
<polygon class="" points="0,0 120,0 60,103.92" style="fill:grey;" data-bind="click: $root.test.bind($data, $data)" />
</svg>
<!-- /ko -->
</div>
</div>
My problem is that only the left half of the triangles is clickable. I think this is due to the (still rectangular) shape of the svg-element. But I have no idea how to fix this. Is there any way to make every triangle clickable in its whole area?
At the moment, all your individual SVGs are overlapping one another and any click that misses a triangle will be swallowed by the parent <svg> element.
The cleanest solution would be to put all your polygons in one big SVG. However there is another way around your problem using the pointer-events property.
Set pointer-events="none" on your <svg> elements so that clicks will pass through them. But you'll also need to set an explicit pointer-events="fill" on your polygons, since otherwise they'll inherit the "none" from their parent SVGs.
var output = document.getElementById("output");
document.getElementById("red").addEventListener("click", function(e) {
output.textContent = "red";
});
document.getElementById("green").addEventListener("click", function(e) {
output.textContent = "green";
});
svg {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
}
polygon {
pointer-events: fill;
}
#output {
margin-top: 120px;
}
<svg width="100px" height="100px">
<polygon points="0,0,100,0,100,100" fill="red" id="red"/>
</svg>
<svg width="100px" height="100px">
<polygon points="0,0,100,100,0,100" fill="green" id="green"/>
</svg>
<div id="output"></div>
You should use one svg tag with both polygons inside it. This way the Square svg elements won't overlap each other:
polygon {
fill: grey;
}
polygon:hover {
fill: #000;
}
<svg height="103.92" width="185">
<polygon points="60,0 0,103.92 120,103.92" />
<polygon points="65,0 185,0 125,103.92" />
</svg>