I want to create a div with a curved top. I want to use clip-path for this. I have created the path in Inkscape, but I don't understand how I need to scale/alter the path so it scales/stretches with the size of the div.
e.g. the black path in this image should be stretched to fill the div with the red border:
I tried various approaches, the conversion to a list of polygons is closest to what I want, but either it's a long list of points in % , or it's blocky. Silly because svg paths have curves that are supported by css.
// taken from https://stackoverflow.com/questions/78172810/algorithm-to-convert-svg-path-to-css-clip-path-polygon
let path = document.querySelector('path');
let pathLength = Math.floor(path.getTotalLength());
let steps = 10;
let scaled = Math.floor(pathLength / steps);
let bbox = path.getBBox();
let points = Object.keys([...new Array(scaled)]).map(num => {
let point = path.getPointAtLength(num * steps);
let x = (point.x / bbox.width * 100).toFixed(2);
let y = (point.y / bbox.height * 100).toFixed(2);
return `${x}% ${y}%`;
}).join(',');
document.querySelector('style[title="s1"]').innerHTML = `.clipped
{clip-path: polygon(${points});}`;
console.log(`.clipped
{clip-path: polygon(${points});}`)
body {
display: block;
}
svg {
background-color: green
}
.clip_inline {
clip-path: path("m 29.15,0.57 h 42.26 c 0,0 28.02,0 28.02,98.85 H 0.58 C 0.06,5.71 29.17,0.57 29.15,0.57 Z");
width: 700px;
height: 100px;
background-color: blue
}
.clip_url {
background-color: green;
clip-path: url(#path1);
}
.clip_url2{
background-color: yellow;
clip-path: url(#svgPath);
}
<style title="s1"></style>
<p>the svg file</p>
<svg
width="100mm"
height="10mm"
version="1.1"
viewBox="0 0 101 101"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<path
d="m 29.15,0.57
h 42.26
c 0,0 28.02,0 28.02,98.85
H 0.58
C 0.06,5.71 29.17,0.57 29.15,0.57
Z"
fill="none"
stroke="#000000"
stroke-linejoin="round"
stroke-miterlimit="4.5"
stroke-width="0.0115947"
style="fill:#000000;stroke:#ff0000;paint-order:stroke fill markers"
id="path1" />
</svg>
<svg height="0" width="0">
<defs>
<clipPath id="svgPath">
<path fill="#FFFFFF" stroke="#000000" stroke-width="1.5794" stroke-miterlimit="10" d="m 29.15,0.57
h 42.26
c 0,0 28.02,0 28.02,98.85
H 0.58
C 0.06,5.71 29.17,0.57 29.15,0.57
Z"/>
</clipPath>
</defs>
</svg>
<p>the d string from the svg copied into the css, too small. path does not get streched to fill div</p>
<div class="clip_inline" style="background-color:blue; "></div>
<p>the svg path converted it to a list of polygons</p>
<div class="clipped" style="background-color: orange; width:300px;height:200px"></div>
<p>clip path via url</p>
<div class="clip_url" style=" width:200px;height:200px"></div>
<p>clip path via url2</p>
<div class="clip_url2" style=" width:400px;height:200px"></div>
When reducing the step size in the polygon conversion I get a very long list of discrete points, but that seems silly, as curves are clearly supported as clip-paths. I just can figure out how to correctly convert a path from an Inkscape created SVG to the clip-path path format. Applying the clip-path via URL fails as well.
Updated Answer
As per the answer which @herrstrietzel posted a link to, setting your clipPathUnits and applying a scale transform is probably what you want to do.
.clip_url2 {
background-color: yellow;
clip-path: url(#svgPath);
}
<svg style="position: absolute;">
<defs>
<clipPath id="svgPath" clipPathUnits="objectBoundingBox" transform="scale(0.01, 0.01)">
<path fill="#FFFFFF" stroke="#000000" stroke-width="1.5794" stroke-miterlimit="10" d="m 29.15,0.57 h 42.26 c 0,0 28.02,0 28.02,98.85 H 0.58 C 0.06,5.71 29.17,0.57 29.15,0.57 Z"/>
</clipPath>
</defs>
</svg>
<p>clip path via url2</p>
<div class="clip_url2" style=" width:400px;height:200px"></div>
Original Answer
Great question. This is one of the aspects of web development which most bugs me as well. We have these great tools for defining complex curves, but it’s so hard to apply them to common HTML use cases. In HTML we simply want the ability to have one curvy edge on an otherwise rectangular box, but we have to resort to tricks and hacks in order to achieve it.
The preserveAspectRatio attribute is helpful, particularly the value none
, which gives the SVG the freedom to distort the viewbox to match the dimensions of the element. This is a must-read article on SVG scaling. Here is a snippet to demonstrate this method in comparison with the polygon-conversion method you used in your question.
// taken from https://stackoverflow.com/questions/78172810/algorithm-to-convert-svg-path-to-css-clip-path-polygon
let path = document.querySelector('path');
let pathLength = Math.floor(path.getTotalLength());
let steps = 10;
let scaled = Math.floor(pathLength / steps);
let bbox = path.getBBox();
let points = Object.keys([...new Array(scaled)]).map(num => {
let point = path.getPointAtLength(num * steps);
let x = (point.x / bbox.width * 100).toFixed(2);
let y = (point.y / bbox.height * 100).toFixed(2);
return `${x}% ${y}%`;
}).join(',');
document.querySelector('style[title="s1"]').innerHTML = `.clipped
{clip-path: polygon(${points});}`;
console.log(`.clipped
{clip-path: polygon(${points});}`)
body {
display: block;
}
.svg1 {
width: 120px;
aspect-ratio: 1;
}
.clip_inline {
clip-path: path("M 0 500 L 0 250 C 0 150 12.925 46.036 71.023 8.394 C 126.75 -27.712 224.23 82.289 275.277 82.289 C 319.174 82.289 384.17 37.448 429.402 56.469 C 491.391 82.537 500 150 500 250 L 500 500 L 0 500 Z");
width: 200px;
height: 200px;
background-color: blue
}
.clipped, .via-absolute {
width: 50%;
color: white;
box-sizing: border-box;
padding: 2em 1em 1px;
display: flex;
align-items: end;
}
.clipped {
background-color: orange;
}
.via-absolute {
position: relative;
}
.via-absolute svg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
}
<style title="s1"></style>
<svg style="position: absolute;">
<defs>
<symbol id="sym1" viewBox="0 0 500 500" preserveAspectRatio="none">
<path style="stroke: rgb(0, 0, 0); stroke-width: 0px; fill: rgb(255, 0, 0);" d="M 0 500 L 0 250 C 0 150 12.925 46.036 71.023 8.394 C 126.75 -27.712 224.23 82.289 275.277 82.289 C 319.174 82.289 384.17 37.448 429.402 56.469 C 491.391 82.537 500 150 500 250 L 500 500 L 0 500 Z" transform="matrix(1, 0, 0, 1, 5.684341886080802e-14, 0)"/>
</symbol>
</defs>
</svg>
<p>the svg path converted to a list of polygons</p>
<div class="clipped">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas tempor nunc mauris, sit amet placerat tortor lobortis dapibus. Nam lectus eros, maximus ac magna vel, congue consequat eros. Fusce id pretium diam.</p>
</div>
<p>via absolute positioning and preserveAspectRatio=none (no script required)</p>
<div class="via-absolute">
<svg><use href="#sym1"/></svg>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas tempor nunc mauris, sit amet placerat tortor lobortis dapibus. Nam lectus eros, maximus ac magna vel, congue consequat eros. Fusce id pretium diam.</p>
</div>
This solution is still far from ideal. What we really need is CSS attributes like border-top-path
and border-bottom-path
to which you could assign a vector path. With these properties you would of course want the path to stretch horizonally, but you would want the ability to control whether or not it stretches vertically as well.