创建反向剪切路径 - CSS 或 SVG

30
我试图创建的东西本质上是CSS clip-path的反向。使用clip-path时,图像或div被剪裁,以便仅保留您指定的形状,其余背景有效地被删除。
如果我剪裁一个形状,它基本上会在最上层打洞并删除形状,而不是背景。这可能吗?我也可以接受SVG解决方案,但我对SVG很陌生,请多关照 :)
基本上,在下面的代码中,我有一个蓝色的正方形绝对定位在一个红色的正方形内部,并希望能够从蓝色正方形中挖出一个形状,使得下面的红色层透过形状所在的位置呈现出来。实际上,背景层将是一张图片,因此我无法接受模仿我想要的效果但实际上不打洞形状的伪效果。
任何帮助都将是惊人的!
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>


1
所以清楚一点,您想在内部盒子中创建一个洞以查看背景吗? - Temani Afif
@TemaniAfif 是的,正确的。 - pjk_ok
4个回答

32

您可以将图片放置在蓝色部分之上,并在其上应用clip-path,这样的结果就像是在蓝色部分内创建了一个洞以查看下方的图片一样:

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>

另一个想法是考虑多个背景,这样您将获得比clip-path更好的支持,并且代码量更少:

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>

更新

如果您想要一些不透明度,这里有一个主意,您需要使用clip-path来复制内容(一个缺点):

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>

更新2

你可以考虑使用SVG来实现您的初始需求。只需使用SVG代替具有掩模的div即可。

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>

您也可以将同一SVG用作背景:

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>

更新3(我在2020年推荐的方法)

您可以使用CSS遮罩和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>

同样形状的反转版本

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>


这非常聪明。但是我确实需要在蓝色图层中打一个洞,因为背景要么是图像,要么是视频,但也会有文本。我需要的是你刚才做的效果,但是将蓝色图层的不透明度设置为0.9,并使图像或视频填充整个容器,但在形状处是100%透明的。我不确定这是否可能。 - pjk_ok
@TheChewy 好的,假设你想使用 clip-path 实现不透明度 :) - Temani Afif
@TheChewy 再检查一下,我添加了另一个想法 :) - Temani Afif
是的,这将适用于图像。如果客户选择视频路线(他们很可能会这样做),我不确定我能否通过这种方式获得好处,因为这意味着加载两个大型视频元素,这将导致性能受到相当大的影响。我将发布一个单独的问题,使用SVG覆盖层并在其中打孔,看看是否可能对其进行响应式对齐。我非常感谢您的帮助,并将考虑上述基于图像的解决方案。 - pjk_ok
@TheChewy 好的,我也会关注那个问题的 :) - Temani Afif

18
这在谷歌上排名很高,但答案没有解决我的问题,因为我无法触摸我的背景图片,所以这里有另一种方法:
创建一个带有剪辑路径的框架。

body {
  width: 100%;
  height: 100vh;
  padding: 0;
  margin: 0;
  display: grid;
  place-items: center;
}

#clip,
#background {
  width: 400px;
  height: 400px;
}

#clip {
  clip-path: polygon(0% 0%, 0% 100%, 25% 100%, 25% 25%, 75% 25%, 75% 75%, 25% 75%, 25% 100%, 100% 100%, 100% 0%);
  position: absolute;
  background: #fff;
  opacity: 0.8;
}

#background {
  background: url(https://picsum.photos/400/400/) center/cover;
  z-index: -1;
}
<div id="background">
  <div id="clip"></div>
</div>

我将clip-div放在图片内部是为了方便,但你也可以将其放在外面。

1
巧妙而简单,谢谢! - agurtovoy

7
为了进一步发挥@leonheess的优秀工作,借助var()和calc(),您可以设置x / y / width / height变量,并根据js熟悉的属性轻松移动正方形。

#clip-container {
  --windowposition-x: 50px;
  --windowposition-y: 50px;
  --windowposition-height: 100px;
  --windowposition-width: 100px;
}

body {
  width: 100%;
  height: 100vh;
  padding: 0;
  margin: 0;
  display: grid;
  place-items: center;
  background: url(https://picsum.photos/400/400/) center/cover;

}

#clip-container {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(197, 185, 185, 0.7);
  clip-path: polygon(0% 0%,
        0% 100%,
        var(--windowposition-x) 100%,
        var(--windowposition-x) var(--windowposition-y),
        calc(var(--windowposition-x) + var(--windowposition-width)) var(--windowposition-y),
        calc(var(--windowposition-x) + var(--windowposition-width)) calc(var(--windowposition-y) + var(--windowposition-height)),
        var(--windowposition-x) calc(var(--windowposition-y) + var(--windowposition-height)),
        var(--windowposition-x) 100%,
        100% 100%,
        100% 0%);
}
  <div id="clip-container"></div>

如果你真的想要的话,甚至可以更进一步,像这样在你的HTML中定义CSS变量:

body {
  width: 100%;
  height: 100vh;
  padding: 0;
  margin: 0;
  display: grid;
  place-items: center;
  background: url(https://picsum.photos/400/400/) center/cover;

}

#clip-container {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(197, 185, 185, 0.7);
  clip-path: polygon(0% 0%,
        0% 100%,
        var(--windowposition-x) 100%,
        var(--windowposition-x) var(--windowposition-y),
        calc(var(--windowposition-x) + var(--windowposition-width)) var(--windowposition-y),
        calc(var(--windowposition-x) + var(--windowposition-width)) calc(var(--windowposition-y) + var(--windowposition-height)),
        var(--windowposition-x) calc(var(--windowposition-y) + var(--windowposition-height)),
        var(--windowposition-x) 100%,
        100% 100%,
        100% 0%);
}
  <div id="clip-container" style="--windowposition-x: 75px;--windowposition-y: 75px;--windowposition-height: 75px;--windowposition-width: 75px;"></div>


有没有办法处理具有n个点作为“孔”的多边形? - Nic Estrada
@NicEstrada - 我不认为通过上述方法可以轻松实现动态数量的顶点,至少在我对 CSS 变量的理解范围之外,很抱歉。 - Josh Mc
我实际上让它工作了(只有在可以假设多边形的点按顺时针顺序列出时),但后来发现我的要求包括需要多个多边形切割,并且实际上我需要使用遮罩。 - Nic Estrada
很好 - 随意在这里回答,可能会对其他人有所帮助 :D - Josh Mc

1

由于这对我没有起作用,我进行了一些测试并解决了我的问题,让我分享一下:

你可以使用带有SVG路径的剪辑路径,使其覆盖您想要出现的所有区域,然后在其中添加另一个反方向的路径来创建孔(难以解释,但很容易做到...)。

您可能需要根据元素的大小在代码上生成此路径,但这很容易完成:

const element = document.getElementById('element')

const width = element.clientWidth
const height = element.clientHeight
const holeX = 50
const holeY = 30
const holeSize = 60

const holePath = `M ${holeX} ${holeY} L ${holeX + holeSize} ${holeY} L ${holeX + holeSize} ${holeY + holeSize} L ${holeX} ${holeY + holeSize} L ${holeX} ${holeY}`

const path = `M 0 0 L 0 ${height} L ${width} ${height} L ${width} 0 L 0 0 ${holePath} Z`

element.style.clipPath = `path('${path}')`
body {  
  background-color: #09f;
}

#element {
  background: url(https://picsum.photos/500/250/);
  width: 500px;
  height: 250px;
}
<div id="element" />

如果你想使用不同的孔洞格式,只需获取一些SVG路径并将其放入holePath中。它应该可以工作。

const element = document.getElementById('element')

const width = element.clientWidth
const height = element.clientHeight
const holeX = 50
const holeY = 150
const holeSize = 60

const holePath = `M ${holeX} ${holeY} v-100 h100 a50,50 90 0,1 0,100 a50,50 90 0,1 -100,0`

const path = `M 0 0 L 0 ${height} L ${width} ${height} L ${width} 0 L 0 0 ${holePath} Z`

element.style.clipPath = `path('${path}')`
body {  
  background-color: #09f;
}

#element {
  background: url(https://picsum.photos/500/250/);
  width: 500px;
  height: 250px;
}
<div id="element" />


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接