使用 CSS 和 SVG 创建 Blob 的三种方法

Avatar of Akash Mittal
Akash Mittal

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

Blob 是光滑、随机、果冻状的形状,具有异想天开的品质,而且非常有趣。它们可以用作 网页上的插图元素和背景效果

那么,它们是如何制作的呢?只需打开一个插图应用程序,然后开始创作,对吧?当然,这很酷。但我们是在 CSS-Tricks 上发表的文章,所以用 CSS 和 SVG 这两种我们最喜欢的工具来探索可能性会更有意思!

实际上,我们有几种方法可以创建 Blob。让我们来试一试。

在 SVG 中绘制圆形

让我们从简单的开始。我们可以在 Illustrator、Sketch、Figma 或其他软件中绘制 SVG,但我们将直接在 SVG 代码中绘制。

由于 <circle> 元素的存在,在 SVG 中绘制圆形非常简单。

<circle cx="100" cy="100" r="40" fill="red" />

那些奇怪的属性?分解后就一目了然了。

  • cx 定义圆心在 x 轴上的坐标。
  • cy 定义圆心在 y 轴上的坐标。
  • r 是半径。
  • fill 用于用颜色填充形状。

这段代码片段创建了一个半径为 40 像素的圆形,圆心在 x 轴上的坐标为 100 像素,在 y 轴上的坐标为 100 像素。坐标从父容器的左上角开始。

让我们创建多个这样的重叠圆形。

<svg height="300" width="300">
  <circle cx="80" cy="80" r="40" fill="red" />
  <circle cx="120" cy="80" r="40" fill="red" />
  <circle cx="150" cy="80" r="40" fill="red" />
  <circle cx="150" cy="120" r="40" fill="red" />
  <circle cx="100" cy="100" r="40" fill="red" />
</svg> 

<svg> 充当画板,所有不同的形状和图形都在上面绘制。因此,它的 heightwidth 指示整个绘图需要包含的大小。如果图形的一部分超出了 SVG 的大小范围,则该部分将被截断。

但是 Blob 并不总是那么完美地……圆形。我们可以使用 <ellipse> 来代替 <circle>,从而打破这种模式。

<ellipse cx="200" cy="80" rx="100" ry="50" fill="red" />

这与圆形几乎完全相同,只是标签名称发生了变化,并且增加了两个半径值来分别定义水平 (rx) 和垂直 (ry) 半径。有趣的是,如果半径值相同,我们仍然可以得到一个完美的圆形。因此,在某种意义上,<ellipse> 更加通用。

而且,如果只需要一个圆形,我们可以直接使用 CSS,而无需 SVG。任何框元素都可以使用 border-radius 变成圆形或椭圆形。

.circle {
  border-radius: 50%;
  height: 50px;
  width: 50px;
}

……但我们稍后再说。

使用 SVG 路径进行自由创作

借助 SVG 的 <path> 标签,我们可以创建任何形状。它就像用铅笔或钢笔绘画一样。您从一个点开始,然后画线、曲线、形状并闭合回路。

路径中有很多数据参数用于不同的任务,例如

  • M – 移动到某个点
  • L – 绘制直线
  • C – 绘制曲线
  • Q – 贝塞尔曲线
  • Z – 闭合路径

Chris 有一篇 非常全面的指南,详细解释了这些参数。

我们只需要曲线 (C) 参数来进行实际绘制。但我们也将移动起点并闭合路径,所以我们也会用到 MZ 参数。

A green blob with four edges that vary in size and shape.
这是一个我使用 SVG 的 <path> 元素创建的随机 Blob 形状。

准备分解它吗?坐标在 <path> 中起着重要作用,所以我们即将看到的内容看起来像是 Google 地图数据被吐进了我们的代码中。但当我们知道它们在做什么时,就会变得更有意义了。

比如这个……

<svg xmlns="http://www.w3.org/2000/svg">
  <path
    fill="#24A148"
    d=""
  />
</svg>

在这里,d 属性存储路径数据。它包含绘制起点、移动方向、遵循形状以及结束位置的信息。例如

<path d="M 10 10 C 20 20, 40 20, 50 10" stroke="black" fill="transparent"/>

它显示我们的路径从坐标 10 10 开始,由前面的 M 表示。然后,它建立了一个 三次贝塞尔曲线 (C),带有两个控制点。贝塞尔曲线就像路径两端的控制手柄,控制它们之间的弯曲程度。我们有两个贝塞尔“手柄”:一个用于曲线的起始位置 (20 20),另一个用于曲线的结束位置 (40 20)。

Show to end points for a black path line with light red lines extended from each endpoint indicating the amount of curve on each point.

让我们利用这些知识来设计我们的 Blob。我绘制的 Blob 实际上有点复杂,包含许多曲线和控制点。很多坐标不是完整的整数,这更让情况变得复杂。但现在我们已经了解了 <path>d 参数的作用以及它用于绘制路径上点的属性,它看起来并不那么可怕了。

但是,嘿,我理解。那么多代码不仅要手动编写,而且还要确保完全正确。我不会因为您使用 类似这样的工具来生成代码 而责怪您。

使用 CSS 和 SVG 滤镜实现果冻效果

SVG 路径很复杂。对吧?如果我向您展示一种将许多自定义形状(您可以通过 div 创建)转换为果冻 Blob 的方法呢?这就是想法。我们将创建两个相交的矩形。它们的颜色相同,但有一点透明度,以使相交处变暗。

然后,我们将利用 SVG 的模糊功能来模糊矩形,从而创建一个具有更柔和边缘的额外果冻 Blob。两个相交的矩形将变成这样 –

A bright red blob with four corners of varying shape and size, like in a reverse L shape.

让我们首先了解 SVG 中的滤镜是如何工作的。它们是使用 <filter> 在 HTML 元素或其他 SVG 元素(如 circle)上声明的。

circle {
  filter: url("#id_of_filter");
}

<filter> 基本上是实际滤镜效果的包装器,其中包括

  • <feGaussianBlur>
  • <feImage>
  • <feMerge>
  • <feColorMatrix>
  • 还有更多……请查看 此处 的完整列表。

我们的 Blob 是模糊的,而且有颜色,所以我们将使用 <feGaussianBlur><feColorMatrix>

<feGaussianBlur> 接受多个属性,但我们只关注其中两个:我们想要的模糊程度以及我们想要模糊的位置。标准差 (stdDeviation) 和 in 属性分别与这些需求一致。

in 接受两个值之一

  • SourceGraphic – 模糊整个形状
  • SourceAlpha – 模糊 alpha 值,用于创建阴影效果

经过一番尝试,我最终得到了 <feGaussianBlur> 效果

<feGaussianBlur in="SourceGraphic" stdDeviation="30" />

这可以直接放在 HTML 标记中,并使用一个 ID,我们在 Blob 的父元素中调用它。

<!-- The SVG filter -->
<svg style="position: absolute; width: 0; height: 0;">
  <filter id="goo">
    <feGaussianBlur in="SourceGraphic" stdDeviation="30" />
  </filter>
</svg>

<!-- The blob -->
<div class="hooks-main">
  <div></div>
  <div></div>
</div>

滤镜实际上不会渲染,即使它在标记中。相反,我们将它作为 Blob 父元素的 CSS 滤镜进行引用。

/* Blob parent element */
.hooks-main {
  position: absolute;
  width: 100%;
  height: 100%;
  filter: url("#goo&amp");
  overflow: hidden;
}

这还没有完成。模糊是分散的,元素的形状失去了边界和颜色。我们需要一种凸出效果,在边界上模糊,并用纯色填充形状。这时,我们的下一个 SVG 滤镜 <feColorMatrix> 就发挥了作用。

我们想要使用 <feColorMatrix> 的两个属性

  • in – 指示效果应用的位置,就像 <feGaussianBlur> 一样。
  • values – 一个四行五列的矩阵。

values 属性稍微复杂一些。它包含一个矩阵,该矩阵将乘以每个像素的颜色和 alpha 值,并为该像素生成新的颜色值。从数学角度来说

new pixel color value = ( values matrix ) × ( current pixel color value )

让我们稍微深入一下数字。在那个方程中,values 矩阵 等于

[F-red1 F-green1 F-blue1 F-alpha1 F-constant1
 F-red2 F-green2 F-blue2 F-alpha2 F-constant2
 F-red3 F-green3 F-blue3 F-alpha3 F-constant3
 F-red4 F-green4 F-blue4 F-alpha4 F-constant4]

这里,F-red 表示像素中红色部分的比例,取值范围为 0 到 1。F-constant 是要添加到颜色值(或从颜色值中减去)的某个常数值。

进一步分解……我们有一个颜色像素,其 RGBA 值为 rgba(214, 232, 250, 1)。为了将其转换为新的颜色,我们将它乘以我们的 values 矩阵。

Values 矩阵颜色像素 (RGBA)新颜色 (RGBA)
[1 0 0 0 0
 0 1 0 0 0
 0 0 1 0 0
 0 0 0 1 0
 0 0 0 0 1]
×[214
 232
 250
 1
 1]
= [ 214x1 + 232x0 + 250x0 + 1x0 + 1x1
      214x0 + 232x1 + 250x0 + 1x0 + 1x1
      214x0 + 232x0 + 250x1 + 1x0 + 1x1
      214x0 + 232x0 + 250x0 + 1x1 + 1x1
      214x0 + 232x0 + 250x0 + 1x0 + 1x1 ]
= [214
  232
  250
 1
  1]

像素值没有改变,因为我们将其乘以 单位矩阵,但如果您更改矩阵的值,则像素值也会发生变化。从 MDN 文档 中了解更多关于 values 矩阵的信息。

在我们这个案例中,这些值似乎很有效。

<filter id="goo">
  <feGaussianBlur in="SourceGraphic" stdDeviation="30" />
  <feColorMatrix
    in="blur"
    values="1 0 0 0 0 
            0 1 0 0 0 
            0 0 1 0 0 
            0 0 0 30 -7"
  />
</filter>

我在 Blob 中添加了一些其他样式,以将其从角落拉伸。

尝试将这些滤镜值应用于其他形状,并在评论中告诉我它们的效果。

使用 CSS border-radius

我们之前已经预告过,现在让我们来谈谈 CSS 的 `border-radius` 属性。它还可以创建水滴状形状,因为它可以平滑元素的角。这是因为每个角的半径被分成两个半径,每个边一个。这就是为什么除了圆形和椭圆形之外,我们还可以拥有更多形状。

Each corner has two radii, one for each edge. For example, the top-left corder of a box has two radii, one for its left edge and one for its top edge.

你可能习惯于使用 `border-radius` 作为元素所有四个角的简写

.rounded {
  border-radius: 25%;
}

这是一种让所有角都保持一致的好方法。但水滴状形状并不那么一致。我们希望有些角比其他角更圆,以便得到看起来像胶状的形状。这就是为什么我们要使用 `border-radius` 的组成属性,比如

.element {
  border-top-left-radius: 70% 60%;
  border-top-right-radius: 30% 40%;
  border-bottom-right-radius: 30% 60%;
  border-bottom-left-radius: 70% 40%;
}

并且看到每个属性都接受两个值?这分别对应于角的每个边,为我们提供了很大的灵活性,可以将元素弯曲成有趣的形状。然后,我们可以添加背景颜色,用渐变填充它,甚至在它上面设置一个 `box-shadow` 来获得一个整洁的效果。