I'm trying to create what is in essence the reverse of a CSS clip-path. When using clip-path, an image or div is clipped so that only the shape you specify remains and the rest of the background is effectively deleted.
I would like it so that if I clip a shape it basically punches a hole in the upper most layer and removes the shape, not the background. Is this possible? I'd also be open to an SVG solution, but I am new to SVG so be kind :)
Basically, in the code below I have a blue square positioned absolutely inside a red square and want to be able to punch a shape out of the blue square so the red layer below shows through where the shape used to be. In reality there will an image as the background layer, so I can't accept a pseudo effect that mimics what I want but doesn't actually punch the shape out.
Any assistance would be amazing!
codepen: https://codepen.io/emilychews/pen/GQmyqx
body {
width: 100%;
height: 100vh;
padding: 0; margin: 0;
display: flex;
}
#box {
margin: auto;
position: relative;
width: 33%;
height: 200px;
background: red;
}
#innerbox {
width: 100%;
height: 100%;
background: blue;
top: 0;
left: 0;
position: absolute;
}
<div id="box">
<div id="innerbox"></div>
</div>
Update 2025
I shared a trick on my blog where you can easily invert any clip-path: polygon()
: https://css-tip.com/cut-out-shapes/
Examples:
.shape {
--shape: 0 0,100% 0,50% 100%,0 0; /* the first value is repeated at the end */
height: 120px;
aspect-ratio: 1;
clip-path: polygon(var(--shape));
background: linear-gradient(-45deg,#CD8C52,#5E9FA3);
}
.shape.invert {
--s: -20px; /* to control the space */
padding: calc(-1*var(--s));
box-sizing: content-box;
clip-path:
polygon(evenodd,var(--s) var(--s),calc(100% - var(--s)) var(--s),calc(100% - var(--s)) calc(100% - var(--s)),var(--s) calc(100% - var(--s)),var(--s) var(--s),var(--shape)) content-box;
}
/* defining the shapes */
.starburst {
--shape: 100% 50%,78.98% 57.76%,93.3% 75%,71.21% 71.21%,75% 93.3%,57.76% 78.98%,50% 100%,42.24% 78.98%,25% 93.3%,28.79% 71.21%,6.7% 75%,21.02% 57.76%,0% 50%,21.02% 42.24%,6.7% 25%,28.79% 28.79%,25% 6.7%,42.24% 21.02%,50% 0%,57.76% 21.02%,75% 6.7%,71.21% 28.79%,93.3% 25%,78.98% 42.24%,100% 50%;
}
.chevron {
--c: 40%;
--shape: 0 0,var(--c) 0,100% 50%,var(--c) 100%,0 100%,calc(100% - var(--c)) 50%,0 0;
aspect-ratio: 3/5;
}
.triangle {
--shape: 100% 0,0 50%,100% 100%,100% 0;
aspect-ratio: 1/2;
}
.pentagon {
--shape: 79.39% 90.45%,20.61% 90.45%,2.45% 34.55%,50% 0%,97.55% 34.55%,79.39% 90.45%;
}
body {
display: grid;
grid-auto-flow: column;
grid-template-rows: auto auto;
place-items: center;
gap: 10px;
}
<div class="shape starburst"></div>
<div class="shape starburst invert"></div>
<div class="shape chevron"></div>
<div class="shape chevron invert"></div>
<div class="shape triangle"></div>
<div class="shape triangle invert"></div>
<div class="shape pentagon"></div>
<div class="shape pentagon invert"></div>
Old answer
You can put the image above the blue part and you apply the clip-path
on it then the result will be the same as if you have created a hole inside the blue part to see the image below:
body {
width: 100%;
height: 100vh;
padding: 0; margin: 0;
display: flex;
}
#box {
margin: auto;
position: relative;
width: 33%;
height: 200px;
background: blue;
}
#innerbox {
background: url(https://picsum.photos/400/400/) center/cover;
position: absolute;
inset: 0;
z-index:1;
clip-path:polygon(10% 10%, 10% 90%, 90% 50%);
}
<div id="box">
<div id="innerbox"></div>
</div>
Another idea is to consider multiple background and you will have better support than clip-path and also less of code:
body {
height: 100vh;
margin: 0;
display: flex;
}
#box {
margin: auto;
position: relative;
width: 33%;
height: 200px;
background:
linear-gradient(to bottom right,#0000 49%,blue 50%) bottom/100% 60%,
linear-gradient(to top right,#0000 49%,blue 50%) top/100% 60%,
linear-gradient(blue,blue) left/20% 100%,
url(https://picsum.photos/400/400/) center/cover;
background-repeat:no-repeat;
}
<div id="box">
</div>
UPDATE
If you want some opacity, here is an idea where you have to duplicate the content using clip-path
(a drawback):
body {
width: 100%;
height: 100vh;
padding: 0; margin: 0;
display: flex;
}
#box {
margin: auto;
position: relative;
width: 33%;
height: 200px;
background: blue;
}
#innerbox,#innerbox-2 {
background: url(https://picsum.photos/400/400/) center/cover;
position: absolute;
inset: 0;
z-index:2;
}
#innerbox {
/* if you initially planned to have x opacity so you need to set 1-x here*/
opacity:0.4;
}
#innerbox-2 {
z-index:1;
clip-path:polygon(10% 10%, 10% 90%, 90% 50%);
animation:animate 5s linear alternate infinite;
}
@keyframes animate {
from {
clip-path:polygon(10% 10%, 10% 90%, 90% 50%);
}
to {
clip-path:polygon(20% 50%, 90% 50%, 80% 10%);
}
}
<div id="box">
<div id="innerbox">
<h1>Title</h1>
<p>Some content</p>
</div>
<div id="innerbox-2">
<h1>Title</h1>
<p>Some content</p>
</div>
</div>
UPDATE 2
You can consider SVG to do your initial requirement. Simply use an SVG instead of a div where you will have a mask.
body {
width: 100%;
height: 100vh;
padding: 0; margin: 0;
display: flex;
}
#box {
margin: auto;
position: relative;
width: 33%;
height: 200px;
background: blue;
background: url(https://picsum.photos/400/400/) center/cover;
}
#innerbox {
position: absolute;
inset: 0;
z-index:1;
}
<div id="box">
<svg viewBox="0 0 200 200" id="innerbox" preserveAspectRatio="none">
<defs>
<mask id="hole">
<rect width="100%" height="100%" fill="white"/>
<!-- the hole defined a polygon -->
<polygon points="20,20 20,180 180,100 " fill="black"/>
</mask>
</defs>
<!-- create a rect, fill it with the color and apply the above mask -->
<rect fill="blue" width="100%" height="100%" mask="url(#hole)" />
</svg>
</div>
You can also use the same SVG as background:
body {
width: 100%;
height: 100vh;
padding: 0; margin: 0;
display: flex;
}
#box {
margin: auto;
position: relative;
width: 33%;
height: 200px;
background: blue;
background: url(https://picsum.photos/400/400/) center/cover;
}
#innerbox {
position: absolute;
inset: 0;
z-index:1;
background:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" preserveAspectRatio="none"><defs><mask id="hole"><rect width="100%" height="100%" fill="white"/> <polygon points="20,20 20,180 180,100 " fill="black"/></mask></defs><rect fill="blue" width="100%" height="100%" mask="url(%23hole)" /></svg>');
}
<div id="box">
<div id="innerbox"></div>
</div>
Update 3 (what I recommend in 2020)
You can use CSS mask to get the effect you want with mask-composite
body {
width: 100%;
height: 100vh;
padding: 0; margin: 0;
display: flex;
}
#box {
margin: auto;
position: relative;
width: 33%;
height: 200px;
background: url(https://picsum.photos/400/400/) center/cover;
}
#innerbox {
position: absolute;
inset: 0;
-webkit-mask:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" preserveAspectRatio="none"><polygon points="20,20 20,180 180,100 " fill="black"/></svg>') 0/100% 100%;
mask:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" preserveAspectRatio="none"><polygon points="20,20 20,180 180,100 " fill="black"/></svg>') 0/100% 100%;
background: blue;
}
<div id="box">
<div id="innerbox"></div>
</div>
And the inverted version using the same shape
body {
width: 100%;
height: 100vh;
padding: 0; margin: 0;
display: flex;
}
#box {
margin: auto;
position: relative;
width: 33%;
height: 200px;
background: url(https://picsum.photos/400/400/) center/cover;
}
#innerbox {
position: absolute;
inset: 0;
-webkit-mask:
url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" preserveAspectRatio="none"><polygon points="20,20 20,180 180,100 " fill="black"/></svg>') 0/100% 100%,
linear-gradient(#fff,#fff);
-webkit-mask-composite:destination-out;
mask:
url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" preserveAspectRatio="none"><polygon points="20,20 20,180 180,100 " fill="black"/></svg>') 0/100% 100%,
linear-gradient(#fff,#fff);
mask-composite:exclude;
background:blue;
}
<div id="box">
<div id="innerbox"></div>
</div>