基于CSS mask和clip-path实现切角的技巧

本文翻译自 Tricks to Cut Corners Using CSS Mask and Clip-Path Properties,略有删改
原作者:Temani Afif

我们最近使用CSS mask属性创建花哨的边框,本文将使用CSS maskclip-path来切元素的四个角!使用多种技巧可以从任何元素的角切割不同的形状。在本文中,我们将考虑创建独特角落形状的现代技术,同时尝试使用可重用代码,该代码允许我们仅通过调整变量即可产生不同的结果。

查看此在线工具,了解我们正在构建的内容。这是一个CSS生成器,你可以选择不同的形状、角落和大小,可实时获得CSS代码!

image.png

我们主要有两种类型的切割:一种是圆形的,一种是倾斜的。对于每个类型,我们可以选择获取完整的形状或纯边框的形状,以及可以选择要切哪个方向的角。

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

圆形切角

对于圆形切割,我们使用radial-gradient()。切割四个角落时,逻辑解决方案则是创建四个渐变,每个角落一个:

image.png

每个圆形切割部分占整个元素尺寸的四分之一。如上图所示,左上角切割代码如下:

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

以上代码的翻译后,即在左上角呈现一个半径为30px圆圈。主要颜色是透明的(#0000),其余是red。其他三个渐变的逻辑相同。这里的关键字circle可以省略,因为已经明确的指定了一个半径值。

就像我在上一篇文章中所做的那样,这次我将使用略大或更小的值,以避免糟糕的视觉效果。在这里,我使用98%而不是100%来避免锯齿状的边缘,51%而不是50%来创建渐变之间的重叠并避免空白。本文继续遵循这一逻辑。在实际开发中,你会发现添加或删除1%或1deg通常会带来良好的视觉效果。

接下来将此逻辑应用于CSS mask属性,效果如下:

image.png

核心CSS代码如下:

.mask {
  -webkit-mask:
    radial-gradient(circle 30px at top    left ,#0000 98%,#000) top    left,
    radial-gradient(circle 30px at top    right,#0000 98%,#000) top    right,
    radial-gradient(circle 30px at bottom left ,#0000 98%,#000) bottom left,
    radial-gradient(circle 30px at bottom right,#0000 98%,#000) bottom right;
  -webkit-mask-size:51% 51%;
  -webkit-mask-repeat:no-repeat;
}

上面的代码有很多数值是重复的,我们可以稍微优化一下该代码:

--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;

这样我们可以对冗余值使用自定义属性,作为个人偏好,我使用位置的数字值而不是关键字。

在生成器中,将使用以下语法,省去mask-sizemask-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;

我们可以使用更少的渐变吗?

当然!一个渐变也可以完成这个效果。以下动图hover效果就是基于一个渐变实现:

2022-11-10 15.26.27.gif

在这里,我们定义了一个没有尺寸的radial-gradient(),默认情况下它是100%的高度和100%宽度。这让我们在中间有一个洞。我们将渐变平移到图像宽度和高度的一半,将孔移动到一个角落。由于默认情况下,CSS mask重复,因此我们在每个角落都得到相同的效果。我们有四个切角,只使用了一个渐变!

这种方法的唯一缺点是,我们需要提前知道元素的宽度和高度。

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

不幸的是,在这里无法做到这一点,因为当与CSS mask-position属性一起使用时,百分比的行为与像素值不同,比较难处理。

我有一个详细的堆栈溢出答案来解释差异。它涉及background-position,但相同的逻辑适用于CSS mask-position属性。

然而,我们可以使用一些技巧来使其与百分比值一起工作,无需知道宽度或高度。当渐变(或背景图层)的宽度和高度等于元素时,我们无法使用百分比值移动它。所以我们需要改变它的尺寸!

定义一个等于99.5% 99.5%的尺寸。将宽度和高度降低了0.5%,此时基本保持相同的视觉结果,因为我们不会注意到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的图像示例:


image.png

使用99.5%和150.5px将产生四舍五入问题,从而破坏计算,导致mask有一定的错位。因此,请谨慎使用这种方法。

有一个只有一个渐变的解决方案,没有四舍五入问题。使用以下代码:

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))

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

  • 第一种方法使用四个渐变,在使用方面没有缺点,它适用于任何类型的元素和尺寸。但它的代码量很冗长。
  • 第二种方法使用一种渐变,但在某些情况下可能会有偏移不准确的情况。它适用于固定尺寸的元素。
  • 第三种方法使用一种渐变,没有四舍五入问题。这是其中的完美方法,但它需要在渐变值内多次使用半径。

只切割部分角落

现在我们已经看到了所有角落的案例,接下来让我们禁用其中一些。使用第一种方法,任何我们想保持未切割的角落,我们只需删除其渐变并调整剩余内容的大小。


image.png

要禁用右上角的时候:

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

在这里可以做多少可能性和组合。如果需要切割N个角(其中N范围从1到4),则使用N个渐变。我们只需要正确设置每个的大小,以便填满整个空间。

只有一个渐变的其他方法呢?我们需要使用另一个渐变!这两种方法只使用一个radial-gradient()来切割角落,因此我们将依靠另一个渐变来“隐藏”切割。我们可以使用带有四个部分的conic-gradient()来完成此效果:

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

conic-gradient()会覆盖radial-gradient(),这样就不会有切角。让我们将conic-gradient()中的一种颜色更改为透明。例如,将右上角改为透明,就是下面的效果:

image.png

核心代码如下:

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

通过将conic-gradient()的颜色从不透明更改为透明,可以实现我们想要切割的角落,并获得各种可能的组合。

  • 要切割四个角落,第一种方法需要四个渐变,而第三个方法只需要一个渐变,所以我们使用后者。
  • 要切割一个角落,第一种方法需要一个渐变,而第三个方法需要两个渐变,因此您可以使用第一个渐变。
  • 要切割两个角落,两者都使用两个渐变。
  • 要切割三个角落,一个方法将使用三个渐变,另一个方法仅使用两个渐变。

以上介绍了每个案例选择适当的方法,我们总共不需要超过两个渐变。但在实际应用中,您应该选择更优化简洁的代码。

圆形边框型切角

接下来实现和上面一样形状的纯边框版本。换句话说,我们要实现了相同的形状,但去掉了填充物,只剩下形状的边框。

这相对有些麻烦,在这里会使用很多渐变处理,同时尽可能的精简它们的数量。在这种情况下,将考虑使用伪元素。只显示边框意味着我们需要隐藏形状的内部“填充”。

一个角

如下图所示,则需要一个径向渐变和两个圆锥渐变:


image.png

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


image.png

如图所示,radial-gradient()创建圆的四分之一,每个conic-gradient()创建两个垂直段来覆盖两侧。使用相同的代码调整几个变量,我们可以获得其他角落的形状。

两个角

对于双角配置,我们有两个情况。

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


image.png

配置几乎与只切一个角落相同,我们添加额外的渐变并更新一些变量。核心代码如下:

 --g: #0000 calc(98% - 10px),#000 calc(100% - 10px) 98%,#0000;
  --mask:
    radial-gradient(farthest-side at 0    0   ,var(--g)) 0    0/40px 40px no-repeat,
    conic-gradient(from 180deg at right 10px top    10px, #0000 0 90deg,#000 0) 100% 0/calc(100% - 40px + 10px) calc(100% - 40px + 10px) no-repeat,
    radial-gradient(farthest-side at 100% 100%,var(--g)) 100% 100%/40px 40px no-repeat,
    conic-gradient(from 0deg   at left  10px bottom 10px, #0000 0 90deg,#000 0) 0 100%/calc(100% - 40px + 10px) calc(100% - 40px + 10px) no-repeat;
  background:linear-gradient(45deg,blue,red);
  mask: var(--mask);

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


image.png

“等等!”你可能会惊呼。“为什么圆锥形渐变会覆盖三面?”。在所有示例中,我们总是使用no-repeat,但在这里,我们可以重复其中之一,以覆盖更多侧面并减少我们使用的渐变数量。所以这里我们使用了repeat-y。


image.png

另外一个你可能想知道一个径向渐变是如何切开两个角落的。为此,我们创建了半个圆圈,放在左上角。然后,通过使用负偏移量,我们切割了两个相邻的角落。可以看下方悬停动图了解这个技巧。


2022-11-10 20.29.02.gif

三个角

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


image.png

四个角

那么切割所有四个角落则需要一个径向渐变和两个线性渐变。


image.png

你可能会想:“我该怎么记住所有这些箱子?!”你不需要记住任何东西,因为你可以使用在线生成器轻松为每个案例生成代码。您只需要了解整体技巧,而不是每个单独的案例。这就是为什么我只详细介绍了第一批配置——其余的只是调整该技巧初始基础的迭代。

请注意,在整个示例中,我们一直遵循一个总体模式:

我们在要切割的角落上添加一个radial-gradient()
我们使用圆conic-gradient()linear-gradient()填充两侧,以创建最终形状。我们可以找到不同的方法来创建相同的形状。我在这篇文章中展示的是我在尝试了许多其他想法后发现最好的方法。如果你认为有更好的方法,欢迎留言分享!

角度切割

接下来我们解决另一种类型的切割形状:角度切割。

我们有两个参数:切割的大小和角度。为了获得形状,我们需要每个角落的conic-gradient()。这种配置与本文启动的示例非常相似。

image.png

以下是一个角落的实现技巧:


image.png

每个角落之间的差异是from位置和位置的额外偏移量为90deg。完整代码如下所示:

--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()并更新另一个角的大小,以填充剩余的空间,就像我们对圆形切割一样。以下是一个角落的外观:

image.png

除了CSS mask外,我们还可以使用CSS clip-path属性来切开角落。每个角落可以用三个点来定义。
![形状由切割两端的两个点组成,中间有一个点形成角度]

image.png

其他角落的值相同,偏移量为100%。这给了我们总共12分的最终代码——每个角3分。

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))
image.png

对于90deg,我们有两种渐变方法,第一种是我们之前详细说明了用一个渐变切割每个角落的方法,最后一种是我们使用一个渐变切割所有角落的方法。我想你知道故事的其余部分:为了解开一些角落,我们将最后一种方法与圆锥形渐变相结合。正如我所说,不需要记住所有的方法和技巧。生成器将为您生成代码,我只是试着让这篇文章尽可能详细,以涵盖所有可能的情况。

最后

我们只是将CSS mask与渐变相结合,以创建一些花哨的形状,而无需使用大量代码!我们还体验过,要达到正确的代码平衡才能获得正确的结果。在此过程中,我们甚至学到了一些技巧,例如将值更改为一个甚至半个单位。CSS超级强大!

正如我们所讨论的,我制作的在线生成器是获取您需要的代码的好地方,而不是手工编写。

看完本文如果觉得有用,记得点个赞支持,收藏起来说不定哪天就用上啦~

专注前端开发,分享前端相关技术干货,公众号:南城大前端(ID: nanchengfe)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容