使用 CSS Mask 和 Clip-Path 属性来修剪角落的小技巧

Avatar of Temani Afif
Temani Afif

DigitalOcean 为您旅程的每个阶段提供云产品。从 $200 免费积分 开始!

我们最近介绍了使用 CSS mask 属性创建 奇特的边框,现在我们将使用 CSS maskclip-path 来修剪角落! 存在许多技巧可以从任何元素的角落修剪不同的形状。在本文中,我们将考虑现代技术来创建独特的角形状,同时尝试使用可重复使用的代码,允许我们通过调整变量来产生不同的结果。

查看 此在线工具,以了解我们正在构建的内容。这是一个 CSS 生成器,您可以在其中选择形状、角落和大小,然后您会立即获得代码!

我们主要有两种类型的修剪:圆形角度修剪。对于每种类型,我们可以获得完整的形状或仅边框形状,更不用说我们可以选择我们想要修剪的角落了。很多组合!

与上一篇文章一样,我们将大量使用 CSS mask 属性。因此,如果您不熟悉它,我建议您在继续之前阅读我写的快速入门

圆形切口

对于圆形或圆角切口,我们将使用 radial-gradient()。要修剪四个角落,逻辑解决方案是为每个角落创建四个渐变

每个渐变都占据了元素尺寸的四分之一。渐变的语法不言自明

radial-gradient(circle 30px at top left, #0000 98%, red) top left;

换句话说,这将在左上角渲染一个半径为 30px 的圆圈。主要颜色是透明的 (#0000),其余颜色是 红色。整个渐变也放置在从元素的左上角开始的位置。其他三个渐变的逻辑相同。由于我们为半径显式指定了一个值,因此可以省略关键字 circle

就像我在上一篇文章中做的那样,这次我将使用稍微大一点或小一点的值,以避免不良的视觉效果。这里,我使用的是 98% 而不是 100%,以避免锯齿状边缘,使用的是 51% 而不是 50%,以在渐变之间创建重叠并避免空白。本篇文章将遵循此逻辑。实际上,您会发现添加或删除 1%1deg 通常会产生不错的视觉效果。

我们将此应用于 CSS mask 属性,我们就完成了!

实际上,我们可以优化一下该代码

--g: #0000 98%,#000;
--r: 30px;
mask:
  radial-gradient(var(--r) at 0    0   ,var(--g)) 0    0,
  radial-gradient(var(--r) at 100% 0   ,var(--g)) 100% 0,
  radial-gradient(var(--r) at 0    100%,var(--g)) 0    100%,
  radial-gradient(var(--r) at 100% 100%,var(--g)) 100% 100%;
mask-size: 51% 51%;
mask-repeat: no-repeat;

这样,我们就为冗余值使用了自定义属性,而且,个人偏好,我使用数值来表示位置而不是关键字。

在生成器中,我将使用以下语法

--g: #0000 98%,#000;
--r: 30px;
mask:
  radial-gradient(var(--r) at 0    0   ,var(--g)) 0    0   /51% 51% no-repeat,
  radial-gradient(var(--r) at 100% 0   ,var(--g)) 100% 0   /51% 51% no-repeat,
  radial-gradient(var(--r) at 0    100%,var(--g)) 0    100%/51% 51% no-repeat,
  radial-gradient(var(--r) at 100% 100%,var(--g)) 100% 100%/51% 51% no-repeat;

简写语法更容易生成,并且整个值可以作为单个自定义属性使用。

如果我们想使用更少的渐变,可以吗?

当然可以!一个渐变就可以完成工作。将鼠标悬停在下面,以查看技巧

这里,我们定义了一个 radial-gradient(),没有指定大小(默认情况下是 100% 高度和 100% 宽度)。这将在中心形成一个洞。我们将渐变平移/移动图像宽度和高度的一半,将洞移动到一个角落。由于默认情况下,CSS 掩码会重复,因此我们在每个角落都会得到相同的洞。我们使用一个渐变就修剪了四个角落!

此方法唯一的缺点是,我们需要事先知道元素的宽度和高度。

我们不能使用 -50% 而不是宽度和高度的一半吗?

不幸的是,我们在这里无法做到这一点,因为百分比在与 CSS mask-position 属性一起使用时,行为与像素值不同。它们很棘手。

我有一个详细的 Stack Overflow 答案,解释了其中的区别。它处理的是 background-position,但相同的逻辑也适用于 CSS mask-position 属性。

但是,我们可以使用一些技巧来使它使用百分比值工作,而无需知道宽度或高度。当渐变(或背景层)的宽度和高度等于元素时,我们无法使用百分比值来移动它。因此,我们需要更改其大小!

我将定义一个大小等于 99.5% 99.5%。我从宽度和高度中减少了 0.5%,以使值不同于 100%,并且同时保持相同的视觉效果,因为我们不会注意到 100%99.5% 之间有很大区别。现在,我们的渐变的大小不同于 100%,我们可以使用百分比值来移动它。

我不会详细介绍所有数学,但要将它移动宽度和高度的一半,我们需要使用此等式

100% * (50/(100 - 99.5)) = 100% * 100 = 10000%

这是一个奇怪的值,但它确实起作用了

mask: radial-gradient(30px,#0000 98%,#000) 10000% 10000%/99.5% 99.5%

如您所见,此技巧非常有效。无论元素的大小如何,我们都可以使用一个渐变来修剪四个角落。但是,此方法在元素的宽度或高度为小数时有一个小缺点。以下是一个宽度等于 150.5px 的图像示例

99.5%150.5px 结合使用会导致舍入问题,从而破坏计算,导致掩码错位。因此,请谨慎使用此方法。

猜猜看?有一个使用一个渐变且没有舍入问题的解决方案。使用以下代码

mask: radial-gradient(30px at 30px 30px,#0000 98%,#000) -30px -30px

诀窍是创建一个位于左上角的洞,并通过负偏移移动它,从而覆盖四个角落。将鼠标悬停在下面,以查看技巧。

此方法非常完美,因为它使用了一个渐变并且没有舍入问题,但它有一个缺点。半径的值使用了 5 次。这不算什么大问题,因为我们可以使用自定义属性来表示它

--r: 30px;
mask: radial-gradient(var(--r) at var(--r) var(--r),#0000 98%,#000) calc(-1*var(--r)) calc(-1*var(--r))

让我们快速回顾一下我们刚刚介绍的三种方法

  • 第一种方法使用四个渐变,并且在使用方面没有缺点。当然,它很冗长,但它适用于任何类型的元素和大小。
  • 第二种方法使用一个渐变,但它可能会在某些特定情况下出现问题。它适用于固定大小的元素。可以随意使用,但可能不那么频繁。
  • 第三种方法使用一个渐变,并且没有舍入问题。它是在所有方法中最好的,但它需要在渐变值中多次使用半径。

该生成器仅支持第一种和第三种方法。

现在我们看到了所有角落的情况,让我们禁用其中一些。使用第一种方法,对于我们想要保留的任何角落,我们只需删除其渐变并调整剩余部分的大小。

要禁用右上角

  • 我们删除了右上角的渐变(蓝色的渐变)。
  • 我们有一个空角落,因此我们增加了红色渐变(或紫色渐变)的大小以覆盖该剩余空间。

完成了!

您可能已经看到了这里有多少种可能性和组合。如果我们想要修剪 N 个角落(其中 N 的范围从 14),我们使用 N 个渐变。我们只需要正确设置每个渐变的大小,以不留任何空间。

那么只有一个渐变的其他方法呢?我们需要另一个渐变!这两种方法仅使用一个 radial-gradient() 来修剪角落,因此我们将依靠另一个渐变来“隐藏”修剪。我们可以为此任务使用具有四个部分的 conic-gradient()

conic-gradient(red 25%, blue 0 50%, green 0 75%, purple 0)

我们将它添加到径向渐变的顶部以获得以下结果

conic-gradient() 覆盖了 radial-gradient(),并且没有修剪任何角落。让我们将 conic-gradient() 中的一种颜色更改为透明色。例如,右上角的颜色

您看到了吗?我们显示了 radial-gradient() 的一个角落,我们最终获得了一个修剪的角落!

现在让我们做同样的事情,但针对左下角。

我想您现在可能明白了其中的诀窍。通过将 conic-gradient() 的颜色从不透明更改为透明,我们显示了我们想要修剪的角落,并获得了各种可能的组合。

现在您可能想知道应该使用哪种方法以及何时使用。正如我所说,我不太推荐第二种方法,因为它存在舍入问题,但您需要根据修剪的数量来使用其他两种方法。

要修剪四个角落,第一种方法需要四个渐变,而第三种方法只需要一个渐变,因此我们使用第三种方法。要修剪一个角落,第一种方法需要一个渐变,而第三种方法需要两个渐变,因此您使用第一种方法。要修剪两个角落,两种方法都需要两个渐变,要修剪三个角落,一种方法需要三个渐变,而另一种方法只需要两个渐变。

通过为每种情况选择适当的方法,我们总共不需要超过两个渐变。我已经详细介绍了所有方法,但最终,您应该为每种情况选择优化的代码。

圆形边框仅剪切

让我们制作上一个形状的边框版本。换句话说,我们实现相同的形状,但去掉填充,这样我们只剩下形状的边框。

这有点棘手,因为我们有不同的情况,需要不同的代码。预警,我会在这里使用大量的渐变,同时寻找机会减少渐变的数量。

需要注意的是,在这种情况下我们将考虑一个伪元素。只显示边框意味着我们需要隐藏形状的内部“填充”。将此应用于主元素也会隐藏内容——这就是为什么这是一个伪元素的好用例。

一个角被切掉

这个需要一个径向渐变和两个圆锥渐变

第一个示例说明了径向渐变(红色)和两个圆锥渐变(蓝色和绿色)。在第二个示例中,我们将它们全部应用到 CSS mask 属性中,以创建具有一个切角的边框形状。

这是一个游戏计划的图表。

如图表所示,radial-gradient() 创建了四分之一圆,每个 conic-gradient() 创建两个垂直段来覆盖两侧。需要注意的是,渐变重叠不是问题,因为我们不会更改 CSS mask-composite 属性值。

使用相同的代码并调整一些变量,我们可以得到其他角的形状。

两个角被切掉

对于两个角的配置,我们有两种情况发生。

在第一种情况下,有两个相对的角,我们需要两个径向渐变和两个圆锥渐变。

配置几乎与只切掉一个角相同:我们添加一个额外的渐变并更新一些变量。

在第二种情况下,有两个相邻的角,在这种情况下,我们需要一个径向渐变,一个圆锥渐变和一个线性渐变。

“等等!”你可能会惊呼。“为什么圆锥渐变覆盖三边?”如果你检查代码,请注意 repeat-y。在所有示例中,我们始终使用 no-repeat,但对于这个,我们可以重复其中一个来覆盖更多边并减少我们使用的渐变数量。

以下是一个仅使用 conic-gradient() 的示例,以了解重复。诀窍是让高度等于 100% 减去边框大小,以便渐变在重复时填充该空间,从而在此过程中覆盖第三边。

你可能想知道一个径向渐变是如何切掉两个角的。为此,我们在左上角创建了一个半圆。然后通过使用负偏移量,我们切掉了两个相邻的角。将鼠标悬停在下方以了解技巧。

三个角被切掉

对于这种配置,我们需要两个径向渐变,一个圆锥渐变和两个线性渐变。

四个角被切掉

它需要一个径向渐变和两个线性渐变来切掉所有四个角。

我听到你在尖叫,“我怎么才能记住所有这些情况?!” 你不需要记住任何东西,因为你可以使用 在线生成器 轻松生成每种情况的代码。你只需要了解整体技巧,而不是每种情况。这就是为什么我只详细介绍了第一个配置——其他的仅仅是调整技巧最初基础的迭代。

注意我们一直在示例中遵循的一般模式

  1. 我们在我们要切掉的角上添加一个 radial-gradient()
  2. 我们使用 conic-gradient()linear-gradient() 填充两侧以创建最终形状。

需要注意的是,我们可以找到不同的方法来创建相同的形状。我在这篇文章中展示的是我在尝试了许多其他想法后发现的最佳方法。你可能会有不同的方法,你认为更好!如果有,请务必在评论中分享!

倾斜剪切

让我们解决另一种类型的剪切形状:**倾斜剪切。**

我们有两个参数:剪切的大小角度。要获得形状,我们需要每个角一个 conic-gradient()。此配置与本文开头介绍的示例非常相似。

这是一个了解技巧的一个角的插图

每个角之间的区别是 from 中的额外偏移量 90degat 位置。完整代码如下

--size: 30px;
--angle: 130deg;

--g: #0000 var(--angle), #000 0;
mask:
  conic-gradient(from calc(var(--angle)/-2 -  45deg) 
    at top    var(--size) left  var(--size),var(--g)) top left,
  conic-gradient(from calc(var(--angle)/-2 + 45deg) 
    at top    var(--size) right var(--size),var(--g)) top right,
  conic-gradient(from calc(var(--angle)/-2 - 135deg) 
    at bottom var(--size) left  var(--size),var(--g)) bottom left,
  conic-gradient(from calc(var(--angle)/-2 + 135deg) 
    at bottom var(--size) right var(--size),var(--g)) bottom right;
mask-size: 51% 51%;
mask-repeat: no-repeat;

如果我们要禁用一个角,我们删除该角的 conic-gradient() 并更新另一个角的大小以完全填充剩余空间,就像我们对圆形剪切所做的那样。以下是它在一个角上的样子

我们可以对所有其他角执行完全相同的操作以获得相同的效果。

除了 CSS mask,我们还可以使用 CSS clip-path 属性来剪切角。每个角都可以用三个点定义。

Zooming in on a corner of the shape showing the three points that form the angled cut.
形状由剪切两端的两个点和它们之间的一个点组成,形成角度。

其他角将具有相同的值,偏移量为 100%。这给了我们最终代码,总共 12 个点——每个角三个点。

/* I will define T = [1-tan((angle-90)/2)]*size */
clip-path: polygon(
  /* Top-left corner */
  0 T, size size,0 T, /* OR 0 0 */
  /* Top-right corner */
  calc(100% - T) 0,calc(100% - size) size,100% T, /* OR  100% 0 */
  /* Bottom-right corner*/
  100% calc(100% - T),calc(100% - size) calc(100% - size), calc(100% - T) 100%, /* OR 100% 100% */
  /* Bottom-left corner */ 
  T 100%, size calc(100% - size),0 calc(100% - T) /* OR 0 100% */
)

请注意代码中的 OR 注释。它定义了如果我们要禁用特定角需要考虑的代码。要剪切一个角,我们使用三个点。要取消剪切一个角,我们使用一个点——这仅仅是该角的坐标。

90deg 特殊情况

当角度等于 90deg 时,我们可以优化渐变版本的代码并依赖更少的渐变。要切掉四个角,我们只需要一个渐变即可

--size: 30px;
mask: 
  conic-gradient(at var(--size) var(--size),#000 75%,#0000 0) 
  0 0/calc(100% - var(--size)) calc(100% - var(--size))

这是否让你想起什么?它与圆形剪切完全相同!对于 90deg,我们有两种渐变方法,第一种是我们之前详细介绍的方法,每个角都使用一个渐变来切掉,最后一种方法是使用一个渐变切掉所有角。我认为你已经知道剩下的故事了:要取消剪切一些角,我们将最后一种方法与圆锥渐变结合起来

两种渐变方法,一种使用剪切路径,我们必须添加一个圆锥渐变?! 我迷路了……

正如我所说,不需要记住所有方法和技巧。生成器 会为你完成生成代码的工作。我只是试图使这篇文章尽可能详细,涵盖所有可能的情况。

边框仅倾斜剪切

哦,我们终于到达了最后一个也是最棘手的形状!这个可以使用渐变或 clip-path 来实现,但让我们使用 clip-path 方法。

如果我们使用渐变方法,事情会变得很复杂,而且代码量很大。以下是一个演示,说明了这一点

总共九个渐变,而且我还没有完成计算。如你所见,边框的厚度不正确,另外,由于渐变的性质及其抗锯齿问题,最终结果并不令人满意。这种方法可能是一个很好的练习,可以挑战渐变的极限,但我建议不要在生产环境中使用它。

所以,回到 clip-path 方法。我们仍然会得到冗长的代码,但这不那么重要,因为生成器可以为我们完成工作,并得到更干净的最终结果。

以下是对路径的概述。我添加了一点间隙,以便更好地看到不同的点,但我们应该有一个点的重叠。

我们有 13 个外部点(黑色点)和 13 个内部点(蓝色点)。

我们计算外角点的方式与计算常规斜角切角的方式相同。但是,对于内角点,我们需要更多的数学运算。不用担心,我会为你省去一些“枯燥”的几何解释。我知道你们大多数人都不想要,但如果你需要深入研究,可以查看生成器的 JavaScript 文件,找到我用来生成形状的代码和数学公式。

180deg 特殊情况

我想指出斜角切角的特殊情况,即使用等于 180deg 的角度。以下是结果:

角上形成一条直线,因此我们可以优化 clip-path 代码。对于完整的形状,我们可以使用 8 个点(每个角两个点)而不是 12 个点。而对于仅边框版本,我们可以使用 18 个点(9 个内点和外点)而不是 26 个点。换句话说,我们可以删除中间点。

仅边框形状也可以使用渐变来制作。但与之前使用 9 个渐变不同,现在我们可以用 4 个线性渐变来实现干净的结果。

结论

我们刚刚将 CSS 蒙版与渐变结合在一起,创建了一些精美的形状,而无需诉诸于黑客手段和大量代码!我们也体验了找到正确代码平衡以获得正确结果所需的工作量。我们甚至在过程中学习了一些技巧,比如将值更改一个或半个单位。CSS 非常强大!

但是,正如我们所讨论的,我制作的在线生成器是一个非常棒的地方,可以获得所需的代码,而不是手动编写。我的意思是,我费尽心思弄清楚这一切是如何工作的,我可能仍然需要参考这篇文章才能记住它是如何组合在一起的。如果你能记住所有这些,太棒了!但是,有一个生成器可以依靠,真是太好了。