蒙版合成:速成课程

Avatar of Ana Tudor
Ana Tudor

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 200 美元的免费额度!

2018 年初,当我开始更深入地研究 CSS 渐变蒙版以创建有趣的视觉效果时(人们会认为如果不使用单个元素和少量 CSS 则无法实现),我了解到一个以前完全不知道的属性:mask-composite

由于这是一个 使用不广泛的属性,因此我找不到任何关于此主题的全面资源。 因此,当我开始更多地使用它并了解更多信息时(有些人可能还记得我在几篇 其他 文章中提到过它),我决定自己创建这样一个资源,于是就有了这篇文章! 在这里,我将介绍 mask-composite 的工作原理、它为何有用、它可以取哪些值、每个值的作用、支持情况以及在不支持的浏览器中的替代方案。

蒙版合成做什么

蒙版合成允许我们使用各种操作将不同的 mask 层组合成一个。 如何组合它们? 逐像素! 让我们考虑两个 mask 层。 我们获取每一对对应的像素,对它们的通道应用特定的合成操作(稍后我们将详细讨论每个可能的运算),并为结果层获取第三个像素。

Illustration showing two corresponding pixels of the two layers being composited, which results in the corresponding pixel of the resulting layer.
蒙版合成两层在像素级的工作原理。

在合成两层时,顶部的层称为,底部的层称为目标,对我来说这实际上没什么意义,因为源听起来像输入,目标听起来像输出,但在这种情况下,它们都是输入,输出是我们作为合成操作结果获得的层。

Illustration showing two layers. The top layer is the source, while the bottom one is the destination.
合成术语。

当我们有多个层时,合成分阶段进行,从底部开始。

在第一阶段,从底部数起的第二层是我们的源,从底部数起的第一个层是我们的目标。 这两层被合成,结果成为第二阶段的目标,其中从底部数起的第三层是源。 将第三层与前两层的合成结果进行合成,得到第三阶段的目标,其中从底部数起的第四层是源。

.
合成多层。

依此类推,直到我们进入最终阶段,其中最顶层的层与下面所有层的合成结果进行合成。

为什么蒙版合成有用

CSS 和 SVG 蒙版都有其局限性、优点和缺点。 我们可以通过使用 CSS 蒙版来规避 SVG 蒙版的局限性,但是,由于 CSS 蒙版的工作方式与 SVG 蒙版不同,因此采用 CSS 路线会让我们无法在没有合成的情况下获得某些结果。

为了更好地理解所有这些,让我们考虑以下西伯利亚虎幼崽的图片

Fluffy Siberian tiger cub dashing through the snow.
我们希望在页面上进行蒙版的图像。

假设我们希望对其获得以下蒙版效果

Image out of which rhombic shapes at the intersection of thick hashes angled in two different directions get shown, while the hash lines are transparent, allowing us to see the element behind.
所需结果。

此特定 mask 保持菱形可见,而分隔它们的线条被蒙版,我们可以透过图像看到后面的元素。

我们还希望此蒙版效果具有灵活性。 我们不希望受图像尺寸或纵横比的限制,并且希望能够轻松地在随图像缩放的 mask 和不随图像缩放的 mask 之间切换(只需将 % 值更改为 px 值即可)。

为此,我们首先需要了解 SVG 和 CSS 蒙版各自的工作原理以及我们可以用它们做什么以及不能做什么。

SVG 蒙版

SVG 蒙版默认情况下是luminance蒙版。 这意味着与 white mask 像素对应的被蒙版元素的像素完全不透明,与 black mask 像素对应的被蒙版元素的像素完全transparent,而与 mask 像素对应的被蒙版元素的像素在亮度方面介于 blackwhite 之间(greypinklime)是半透明的。

用于从给定 RGB 值中获取亮度的公式为
.2126·R + .7152·G + .0722·B

对于我们的特定示例,这意味着我们需要使菱形区域为 white,将分隔它们的线条设为 black,从而创建如下所示的图案

Pattern with white rhombic shapes at the intersection of thick black hashes angled in two different directions.
用作 SVG 蒙版的黑白菱形图案。

为了获得上面的图案,我们从一个 white SVG 矩形元素 rect 开始。 然后,人们可能会认为我们需要绘制很多黑线……但我们不需要! 相反,我们只需添加一个由该矩形的两条对角线组成的 path,并确保其 strokeblack

要创建第一条对角线(左上到右下),我们使用“移动到”(M)命令到左上角,然后使用“连接到”(L)命令到右下角。

要创建第二条对角线(右上到左下),我们使用“移动到”(M)命令到右上角,然后使用“连接到”(L)命令到左下角。

我们到目前为止的代码是

svg(viewBox=[0, 0, w, h].join(' '))
  rect(width=w height=h fill='#fff')
  path(d=`M0 0
          L${w} ${h} 
					
          M${w} 0
          L0 ${h}` stroke='#000')

到目前为止的结果似乎与我们想要获得的菱形图案没有任何相似之处……

查看 thebabydino (@thebabydino) 在 CodePen 上的 Pen

……但这一点即将改变! 我们增加 black 对角线的粗细(stroke-width),并使它们成为虚线,虚线之间的间隙(7%)大于虚线本身(1%)。

svg(viewBox=[0, 0, w, h].join(' '))
  rect(width=w height=h fill='#fff')
  path(d=`M0 0
          L${w} ${h} 
					
          M${w} 0
          L0 ${h}` stroke='#000' 
       stroke-width='15%'
       stroke-dasharray='1% 7%')

你现在能看出这是怎么回事了吗?

查看 thebabydino (@thebabydino) 在 CodePen 上的 Pen

如果我们继续增加 black 对角线的粗细(stroke-width)到 150% 这样的值,那么它们最终会覆盖整个矩形并给我们带来我们一直想要的图案!

查看 thebabydino (@thebabydino) 在 CodePen 上的 Pen

现在我们可以将 rectpath 元素包装在 mask 元素中,并将此 mask 应用于我们希望的任何元素——在我们的例子中,是老虎图像。

svg(viewBox=[0, 0, w, h].join(' '))
  mask#m
    rect(width=w height=h fill='#fff')
    path(d=`M0 0
            L${w} ${h} 
					
            M${w} 0
            L0 ${h}` stroke='#000' 
         stroke-width='15%'
         stroke-dasharray='1% 7%')
img(src='image.jpg' width=w)
img { mask: url(#m) }

以上应该可以工作。 但遗憾的是,在实践中情况并非如此完美。 在这一点上,我们只在 Firefox 中获得了预期的结果(现场演示)。 更糟糕的是,在 Chrome 中没有获得所需的蒙版图案并不意味着我们的元素保持原样未被蒙版——应用此 mask 会使其完全消失! 当然,由于 Chrome 需要 mask 属性(在 HTML 元素上使用时)的 -webkit- 前缀,因此不添加前缀意味着它甚至不会尝试将 mask 应用于我们的元素。

对于 img 元素最直接的解决方法是将它们转换为 SVG image 元素。

svg(viewBox=[0, 0, w, h].join(' ') width=w)
  mask#m
    rect(width=w height=h fill='#fff')
    path(d=`M0 0
            L${w} ${h} 
					
            M${w} 0
            L0 ${h}` stroke='#000' 
         stroke-width='15%'
         stroke-dasharray='1% 7%')
  image(xlink:href=url width=w mask='url(#m)')

查看 thebabydino (@thebabydino) 在 CodePen 上的 Pen

这给了我们我们一直想要的结果,但如果我们想要 mask 另一个 HTML 元素而不是 img 元素,事情就会变得稍微复杂一些,因为我们需要使用 foreignObject 将其包含在 SVG 内。

更糟糕的是,使用此解决方案,我们正在硬编码尺寸,这总是让人感觉不舒服。

当然,我们可以使 mask 大得离谱,这样它不太可能覆盖任何图像。 但这感觉就像硬编码尺寸一样糟糕。

我们还可以尝试通过将 maskContentUnits 切换到 objectBoundingBox 来解决硬编码问题

svg(viewBox=[0, 0, w, h].join(' '))
  mask#m(maskContentUnits='objectBoundingBox')
    rect(width=1 height=1 fill='#fff')
    path(d=`M0 0
            L1 1 
					
            M1 0
            L0 1` stroke='#000' 
         stroke-width=1.5
         stroke-dasharray='.01 .07')
  image(xlink:href=url width='100%' mask='url(#m)')

但我们仍然在 viewBox 中硬编码尺寸,虽然它们的实际值并不重要,但它们的纵横比很重要。 此外,我们的蒙版图案现在是在一个 1x1 正方形内创建的,然后拉伸以覆盖被蒙版元素。

形状拉伸意味着形状失真,这就是为什么我们的菱形不再像以前那样看起来的原因。

查看 thebabydino (@thebabydino) 在 CodePen 上的 Pen

呃。

我们可以调整构成我们路径的两条线的起点和终点

svg(viewBox=[0, 0, w, h].join(' '))
  mask#m
    rect(width=1 height=1 fill='#fff')
    path(d=`M-.75 0
            L1.75 1 
					
            M1.75 0
            L-.75 1` stroke='#000' 
         stroke-width=1.5
         stroke-dasharray='.01 .07')
  image(xlink:href=url width='100%' mask='url(#m)')

查看 thebabydino (@thebabydino) 在 CodePen 上的 Pen

但是,为了获得特定的菱形图案,以及菱形的特定角度,我们需要知道图像的纵横比。

唉。 让我们放弃它,看看我们能用 CSS 做什么。

CSS 遮罩

CSS 遮罩默认情况下是alpha遮罩。这意味着被遮罩元素的像素对应于完全不透明的mask像素将完全不透明,被遮罩元素的像素对应于完全透明mask像素将完全透明,而被遮罩元素的像素对应于半透明的mask像素将是半透明的。基本上,被遮罩元素的每个像素都会获取对应mask像素的 alpha 通道。

对于我们的特定情况,这意味着使菱形区域不透明,并使分隔它们的线条透明,所以让我们看看如何使用 CSS 渐变来实现这一点!

为了获得带有白色菱形区域和黑色分隔线的图案,我们可以分层使用两个重复线性渐变

查看 thebabydino 在 CodePen 上创建的 Pen@thebabydino)。

repeating-linear-gradient(-60deg, 
  #000 0, #000 5px, transparent 0, transparent 35px), 
repeating-linear-gradient(60deg, 
  #000 0, #000 5px, #fff 0, #fff 35px)

如果我们有一个luminance mask,那么这就是可以完成工作的图案。

但在alpha mask的情况下,不是黑色像素给我们完全透明,而是透明的像素。并且不是白色像素给我们完全不透明,而是完全不透明的像素 - 红色黑色白色……它们都可以完成工作!我个人倾向于使用红色棕褐色,因为这意味着只需输入三个字母,输入的字母越少,发生糟糕的拼写错误(可能需要半小时才能调试)的机会就越少。

所以第一个想法是应用相同的技术来获得不透明的菱形区域和透明的分隔线。但这样做,我们遇到了一个问题:第二层渐变的不透明部分覆盖了我们希望保持透明的第一层的部分,反之亦然。

查看 thebabydino 在 CodePen 上创建的 Pen@thebabydino)。

所以我们得到的结果与不透明的菱形区域和透明的分隔线相差甚远。

我最初的想法是使用带有白色菱形区域和黑色分隔线的图案,并结合将mask-mode设置为luminance来解决问题,使 CSS 遮罩像 SVG 遮罩一样工作。

但是,此属性仅受 Firefox 支持,尽管 WebKit 浏览器有非标准的mask-source-type。遗憾的是,支持甚至不是最大的问题,因为标准的 Firefox 方法和非标准的 WebKit 方法都不能给我们想要的结果(实时演示)。

幸运的是,mask-composite可以帮上忙!所以让我们看看此属性可以取哪些值以及每个值的效果。

mask-composite的值及其作用

首先,我们为我们的mask和我们要遮罩的图像确定两个渐变层。

我们用来说明此属性每个值如何工作的两个渐变mask层如下所示

--l0: repeating-linear-gradient(90deg, 
          red, red 1em, 
          transparent 0, transparent 4em);
--l1: linear-gradient(red, transparent);

mask: var(--l1) /* top (source) layer */, 
      var(--l0) /* bottom (destination) layer */

这两个层可以在下面的 Pen 中看作是background渐变(请注意,body有一个散列的background,以便透明和半透明的渐变区域更明显)

查看 thebabydino 在 CodePen 上创建的 Pen@thebabydino)。

顶部的层(--l1)是源,底部的层(--l0)是目标。

我们将mask应用于这张美丽东北豹的图像。

Gorgeous Amur leopard looking up towards the camera.
我们应用mask的图像。

好了,现在我们已经解决了这个问题,让我们看看每个mask-composite值的效果!

add

这是初始值,它给我们与根本不指定mask-composite相同的效果。在这种情况下发生的事情是渐变一个叠加在另一个之上,然后应用结果mask

请注意,在半透明mask层的情况下,尽管值名称为“add”,但 alpha 值并不是简单地相加。而是使用以下公式,其中α₁是源(顶部)层中像素的 alpha,α₀是目标(底部)层中对应像素的 alpha

α₁ + α₀ – α₁·α₀

至少一个 mask层完全不透明(其 alpha 为1)的地方,结果mask将完全不透明,并且被遮罩元素的对应像素将完全不透明地显示(alpha 为1)。

如果源(顶部)层完全不透明,则α₁1,在上面的公式中替换后,我们得到

1 + α₀ - 1·α₀ = 1 + α₀ - α₀ = 1

如果目标(底部)层完全不透明,则α₀1,我们得到

α₁ + 1 – α₁·1 = α₁ + 1 – α₁ = 1

两个 mask层都完全透明(其 alpha 为0)的地方,结果mask将完全透明,因此被遮罩元素的对应像素也将完全透明(alpha 为0)。

0 + 0 – 0·0 = 0 + 0 + 0 = 0

下面,我们可以看到这对我们正在使用的mask层意味着什么 - 合成结果得到的层是什么样子,以及将其应用于我们的东北豹图像产生的最终结果。

Three column illustration of mask-composite: add in action. On the first column, we have the individual gradient layers (both the visual results and the generating code). On the second column, we can see what the layer resulting as a result of compositing using the add operation looks like. And on the third column, we see this resulting mask layer applied on an image of an Amur leopard. Wherever either layer is fully opaque, our image is fully opaque (not masked out at all). Wherever both layers are fully transparent, our image is fully transparent (completely masked out) as well.
对两个给定层使用mask-composite: add的效果。

subtract

名称指的是从源(上层)中“减去”目标(下层)。同样,这并不意味着简单地截断减法,而是使用以下公式

α₁·(1 – α₀)

上述公式意味着,由于任何与0相乘的结果都是0,因此在源(顶部)层完全透明或目标(底部)层完全不透明的地方,结果mask也将完全透明,并且被遮罩元素的对应像素也将完全透明

如果源(顶部)层完全透明,则将其 alpha 在我们的公式中替换为0,我们得到

0·(1 – α₀) = 0

如果目标(底部)层完全不透明,则将其 alpha 在我们的公式中替换为1,我们得到

α₁·(1 – 1) = α₁·0 = 0

这意味着使用先前定义的mask并将mask-composite设置为subtract,我们将得到以下结果

Three column illustration of mask-composite: subtract in action. On the first column, we have the individual gradient layers (both the visual results and the generating code). On the second column, we can see what the layer resulting as a result of compositing using the subtract operation looks like. And on the third column, we see this resulting mask layer applied on an image of an Amur leopard. Wherever the top (source) layer is fully opaque or the bottom (destination) layer is fully transparent, our image is fully transparent (completely masked out) as well.
对两个给定层使用mask-composite: subtract的效果。

请注意,在这种情况下,公式不是对称的,因此,除非α₁α₀相等,否则如果我们交换两个 mask 层,我们将不会得到相同的结果α₁·(1 – α₀)α₀·(1 – α₁)不同)。这意味着如果我们交换两个层的顺序,我们将得到不同的视觉效果!

Three column illustration of mask-composite: subtract in action. On the first column, we have the individual gradient layers (both the visual results and the generating code), swapped - the one that was previously on top is now at the bottom and the other way around. On the second column, we can see what the layer resulting as a result of compositing using the subtract operation looks like. And on the third column, we see this resulting mask layer applied on an image of an Amur leopard.
在交换两个给定层时使用mask-composite: subtract

intersect

在这种情况下,我们只看到两个mask层相交处被遮罩元素的像素。使用的公式是两个层的 alpha 之间的乘积

α₁·α₀

上述公式的结果是,在任何一个 mask层完全透明(其 alpha 为0)的地方,结果mask也将完全透明,被遮罩元素的对应像素也是如此。

如果源(顶部)层完全透明,则将其 alpha 在我们的公式中替换为0,我们得到

0·α₀ = 0

如果目标(底部)层完全透明,则将其 alpha 在我们的公式中替换为0,我们得到

α₁·0 = 0

此外,在两个 mask层都完全不透明(其 alpha 为1)的地方,结果mask将完全不透明,被遮罩元素的对应像素也是如此。这是因为,如果两个层的 alpha 都为1,我们得到

1·1 = 1

在我们的mask的特定情况下,设置mask-composite: intersect意味着我们有

Three column illustration of mask-composite: subtract in action. On the first column, we have the individual gradient layers (both the visual results and the generating code). On the second column, we can see what the layer resulting as a result of compositing using the intersect operation looks like. And on the third column, we see this resulting mask layer applied on an image of an Amur leopard. Wherever both mask layers are fully opaque, our image is fully opaque (not masked out at all). Wherever either mask layer is fully transparent, our image is fully transparent (completely masked out) as well.
对两个给定层使用mask-composite: intersect的效果。

exclude

在这种情况下,每个层基本上都从另一个层中排除,公式为

α₁·(1 – α₀) + α₀·(1 – α₁)

在实践中,此公式意味着,在两个 mask层都完全透明(其 alpha 为0)或完全不透明(其 alpha 为1)的地方,结果mask将完全透明,被遮罩元素的对应像素也将完全透明

如果两个mask层都完全透明,则将它们的 alpha 在我们的公式中替换为0,结果为

0·(1 – 0) + 0·(1 – 0) = 0·1 + 0·1 = 0 + 0 = 0

如果两个mask层都完全不透明,则将它们的 alpha 在我们的公式中替换为1,结果为

1·(1 – 1) + 1·(1 – 1) = 1·0 + 1·0 = 0 + 0 = 0

这也意味着,在其中一层完全透明(其 alpha 为0),而另一层完全不透明(其 alpha 为1)的地方,则结果mask将完全不透明,被遮罩元素的对应像素也是如此。

如果源(顶部)图层完全透明,而目标(底部)图层完全不透明,将α₁替换为0,并将α₀替换为1,则得到

0·(1 – 1) + 1·(1 – 0) = 0·0 + 1·1 = 0 + 1 = 1

如果源(顶部)图层完全不透明,而目标(底部)图层完全透明,将α₁替换为1,并将α₀替换为0,则得到

1·(1 – 0) + 0·(1 – 1) = 1·1 + 0·0 = 1 + 0 = 1

使用我们的mask,设置mask-composite: exclude意味着我们有

Three column illustration of mask-composite: subtract in action. On the first column, we have the individual gradient layers (both the visual results and the generating code). On the second column, we can see what the layer resulting as a result of compositing using the exclude operation looks like. And on the third column, we see this resulting mask layer applied on an image of an Amur leopard. Wherever one layer is fully opaque and the other one fully transparent, our image is fully opaque (not masked out at all) too. Wherever both layers are either fully opaque or fully transparent, our image is fully transparent (completely masked out) as well.
使用mask-composite: exclude对两个给定图层进行的操作。

将其应用于我们的用例

我们回到两个试图获得菱形图案的渐变

--l1: repeating-linear-gradient(-60deg, 
          transparent 0, transparent 5px, 
          tan 0, tan 35px);
--l0: repeating-linear-gradient(60deg, 
          transparent 0, transparent 5px, 
          tan 0, tan 35px)

如果我们将完全不透明的部分(在本例中为tan)设置为半透明(假设为rgba(tan, .5)),视觉结果将让我们了解合成在这里如何提供帮助

$c: rgba(tan, .5);
$sw: 5px;

--l1: repeating-linear-gradient(-60deg, 
          transparent 0, transparent #{$sw}, 
          #{$c} 0, #{$c} #{7*$sw});
--l0: repeating-linear-gradient(60deg, 
          transparent 0, transparent #{$sw}, 
          #{$c} 0, #{$c} #{7*$sw})

查看thebabydino在CodePen上创建的Pen@thebabydino)。

我们想要的菱形区域形成于半透明条带的交点处。这意味着使用mask-composite: intersect应该可以解决问题!

$sw: 5px;

--l1: repeating-linear-gradient(-60deg, 
          transparent 0, transparent #{$sw}, 
          tan 0, tan #{7*$sw});
--l0: repeating-linear-gradient(60deg, 
          transparent 0, transparent #{$sw}, 
          tan 0, tan #{7*$sw});
mask: var(--l1) intersect, var(--l0)

请注意,我们甚至可以将合成操作包含在简写中!我真的很喜欢这一点,因为越少浪费至少十分钟的时间去理解为什么masj-compositemsdk-compositenask-compositemask-comoisite等等不起作用,就越好!

这不仅给我们带来了期望的结果,而且,如果现在我们将透明条带宽度存储到一个变量中,将此值更改为%值(例如$sw: .05%),则蒙版会随着图像缩放!

如果透明条带宽度为px值,则菱形形状和分隔线的大小在图像随视口缩放时保持不变。

Screenshot collage. Shows the masked image using a non-scaling mask (transparent strips have a px-based width) in the wide viewport case (left) and in the narrow viewport case (right). The opaque rhombic areas and the transparent strips have the same dimensions in both cases, even though the image has scaled down with the viewport in the narrow screen case.
当菱形形状之间的透明分隔线具有px值的宽度时,在两个不同的视口宽度下的蒙版图像。

如果透明条带宽度为%值,则菱形形状和分隔线的大小相对于图像,因此会随图像一起缩放。

Screenshot collage. Shows the masked image using a scaling mask (transparent strips have a %-based width) in the wide viewport case (left) and in the narrow viewport case (right). The opaque rhombic areas and the transparent strips have scaled down with the image in the narrow screen case.
当菱形形状之间的透明分隔线具有%值的宽度时,在两个不同的视口宽度下的蒙版图像。

太好了,是真的吗?对此的支持情况如何?

坏消息是,目前只有Firefox支持mask-composite。好消息是我们有适用于WebKit浏览器的替代方案,因此我们可以扩展支持。

扩展支持

WebKit浏览器支持(并且已经支持了很长时间)此属性的非标准版本-webkit-mask-composite,它需要不同的值才能工作。这些等效值是

  • source-over对应add
  • source-out对应subtract
  • source-in对应intersect
  • xor对应exclude

因此,为了获得跨浏览器版本,我们只需要添加WebKit版本,对吧?

好吧,遗憾的是,事情并没有那么简单。

首先,我们不能在-webkit-mask简写中使用此值,以下方法无效

-webkit-mask: var(--l1) source-in, var(--l0)

如果我们将合成操作从简写中取出,并在其后编写完整形式,如下所示

-webkit-mask: var(--l1), var(--l0);
-webkit-mask-composite: source-in;
        mask: var(--l1) intersect, var(--l0)

……整个图像完全消失了!

如果你认为这很奇怪,请查看:使用其他三个操作中的任何一个add/ source-oversubtract/ source-outexclude/ xor,我们都可以在WebKit浏览器和Firefox中获得预期的结果。只有source-in值在WebKit浏览器中导致问题!

查看thebabydino在CodePen上创建的Pen@thebabydino)。

怎么回事?!

为什么这个特定值会导致WebKit出现问题?

当我第一次遇到这个问题时,我花了几分钟时间试图在source-in中找到错别字,然后从参考中复制粘贴它,然后从第二个参考中复制粘贴,以防第一个参考出错,然后从第三个参考中复制粘贴……最后我有了另一个想法!

似乎在非标准WebKit替代方案的情况下,我们还在底部图层与其下方的一层空白(被认为完全透明)之间应用了合成。

对于其他三个操作,这绝对没有任何区别。实际上,添加、减去或排除空白不会改变任何东西。如果我们采用这三个操作的公式,并将α₀替换为0,我们总是得到α₁

  • add/ source-over: α₁ + 0 – α₁·0 = α₁ + 0 - 0 = α₁
  • subtract/ source-out: α₁·(1 – 0) = α₁·1 = α₁
  • exclude/ xor: α₁·(1 – 0) + 0·(1 – α₁) = α₁·1 + 0 = α₁

但是,与空白的交集是另一回事。与空白的交集是空白!这也通过在intersect/ source-in操作的公式中用0替换α₀来证明

α₁·0 = 0

在这种情况下,结果图层的alpha为0,因此难怪我们的图像会被完全遮罩!

因此,想到的第一个解决方法是使用另一个操作(其他三个中的任何一个都可以,我选择xor因为它字母较少,并且可以通过双击完全选中)将底部图层与其下方的空白层进行合成

-webkit-mask: var(--l1), var(--l0);
-webkit-mask-composite: source-in, xor;
        mask: var(--l1) intersect, var(--l0)

是的,这确实有效!

您可以调整下面嵌入的大小,以查看mask在随图像缩放和不缩放时如何表现。

查看thebabydino在CodePen上创建的Pen@thebabydino)。

请注意,我们需要在标准版本之前添加非标准的WebKit版本,以便当WebKit浏览器最终也实现标准版本时,这会覆盖非标准版本。

好吧,就是这样!希望您喜欢这篇文章,并从中学习到新东西。

更多演示

在结束之前,这里还有两个演示展示了为什么mask-composite很酷。

第一个演示展示了一堆1个元素的雨伞。每个“缺口”都是用一个radial-gradient()创建的,我们将其从完整的圆形形状中exclude。Chrome有一点渲染问题,但在Firefox中结果看起来完美无缺。

Screenshot. Shows a bunch of pastel umbrella shapes as if seen from above on top of the image of a beach.
使用mask-composite的1个元素雨伞(实时演示)。

第二个演示展示了三个1个元素的加载器(尽管只有后两个使用mask-composite)。请注意,animation仅在Chrome中有效,因为它需要Houdini。

Animated gif. Shows three gradient loaders made up of segments that vary in opacity over the course of the loading animation.
使用mask-composite的1个元素加载器(实时演示)。

您呢——您能想到哪些其他用例?