使用 SVG 过滤器来做字体特效

1,320 阅读9分钟

本文主要为 SVG 过滤器的人话解释和实践,不会有任何未知知识造成的心理不适,通过阅读可以学会 SVG 过滤器的常用操作。

SVG 过滤器基础知识

SVG 意为可缩放矢量图形,通过 XML 格式定义图像,在 HTML 开发中有广泛的使用。其中 SVG 过滤器可以用来给图形添加一些特殊效果,例如模糊、阴影、色彩转换等等。

滤镜效果将图形操作(例如模糊、照明、颜色转换和扭曲)应用于内容。现在流行的过滤器就两种:

  • CSS 过滤器:CSS 的属性 filter 将模糊或颜色偏移等图形效果应用于元素。滤镜通常用于调整图像、背景和边框的渲染。可应用于任何 HTML 内容。

  • SVG 过滤器:可应用于 SVG 内容(以及通过 CSS 过滤器交叉引用的 HTML 内容)的图形效果组合。 SVG 过滤器的扩展性相比 CSS 过滤器要多一些选择。

为什么要使用 SVG 过滤器

运行时效果动画化,可以防止大型图像下载对性能的影响(例如噪声纹理)。与 Canvas 和 CSS 效果相比,SVG 过滤器的优点在于:

  • 无需下载大图像即可将纹理添加到 SVG 图形。

  • Web 应用程序中嵌入客户端图像编辑功能。

  • 为文本添加光照、浮雕和变形效果,无需将文本转换为图像,保持可访问性和可搜索性。

基础布局和基本参数

  • defs: SVG 允许我们定义以后需要重复使用的图形元素。 可以把所有需要再次使用的引用元素都放在 defs 元素里。这样做可以增加 SVG 内容的易读性和可访问性。
  • filterfilter 元素作用是作为滤镜操作的容器。过滤器需要有一个 id 属性,它不能直接呈现。可以利用目标 SVG 元素上的 filter 属性引用一个滤镜。
<svg>
  <defs>
     <filter id="filter">
       <!-- fe 的各种元素放在这里--> 
    </filter>
  </defs>
  <text class="filtered" x="20" y="140">SVG FONT DEMO</text>
</svg>

那基础框架写完了, 怎么让滤镜和我们的文本做关联呢,我们可以通过 CSS 将文本和 filter 做绑定,这样过滤器的效果就会复用在我们的文本元素中了。

不过这里要多说一嘴,SVG 过滤可用于创建图像特殊效果。过滤器处理光栅图像而不是矢量图形。因此,本文中的所有内容都讨论像素和图像,而不是矢量!

.filtered {
  filter: url(#filter);
}

然后我们要真正实现过滤器,我们就要在 filter 添加具体的滤镜,常见的滤镜有以下几种

字段解释
feMerge合并滤镜,可以使 SVG 同时使用多个滤镜效果
feMorphology主要用来扩张输入的图像,使图像加粗或变瘦
feComposite组合两个输入的图像,可以对图像取交集或者并集
feTurbulence给图像增加纹理
feDisplacementMap对图像做映射
feColorMatrix对颜色做矩阵变换,其实就是输出一个颜色
feOffset偏移图像位置
feFlood填充图像
feBlend组合两个图像到一个图层
feGaussianBlur高斯模糊图像
feImage可以使用JPG、PNG、SVG文件或SVG元素作输入源

举一个很简单的例子,我们想用 SVG 过滤器实现一个 div 的模糊处理,我们可以通过如下代码来实现

一般过滤器模块都会有一个 in 参数和一个 result 参数作为输入输出,如果我们想要使用原始图像我们只需要 in="SourceGraphic" 载入原始图形即可。

<div class="box" style="filter: url(#filter)"></div>
<svg>
  <filter id="filter">
    <feGaussianBlur in="SourceGraphic" stdDeviation="3"></feGaussianBlur>
  </filter>
</svg>


是不是感觉很简单,现在我们动手实践一个 SVG 过滤器模块的案例

使用 SVG 过滤器对字体做特效

首先先来看一下我们要做的结果,这个效果完全就是基于过滤器实现的,接下来我们就来一步一步实现这个字体并介绍过滤器的具体使用方法

step1: 完成基础过滤器配置

我们先来写一个框架,定义一个需要改造的字体文案

 <svg
      version="1.1"
      xmlns="http://www.w3.org/2000/svg"
      xmlns:xlink="http://www.w3.org/1999/xlink"
    >
  <defs>
    <filter id="filter">
       <!-- 待填充 -->
    </filter>
  </defs>
  <text class="filtered" x="20" y="140">SVG FONT DEMO</text>
</svg>
svg {
  display: block;
  position: relative;
  width: 1000px;
  height: 200px;
  top: 50%;
  transform: translateY(-50%);
  margin: 0 auto;
  overflow: hidden;
}

.filtered {
  filter: url(#filter);
  fill: #9673ff;
  color: #9673ff;
  font-weight: 900;
  font-size: 90px;
}

到这里我们还是什么都看不到,因为我们一个滤镜都没添加,现在我们来往里一个个添加

step2: 绘制边框

feMorphology 滤镜

使用 feMorphology 先来改变字体粗细,这里定义 feMorphology 的输出值为 OUTLINE_10 方便后续使用,具体的值通过 radius 设置,radius 有两个值。

  • erode 定义的源图形变细
  • dilate 定义的源图形变胖
<filter>
  <feMorphology
    operator="dilate"
    radius="5"
    in="SourceAlpha"
    result="OUTLINE_10"
  />
  <!-- 待填充 -->
</filter>

feComposite 滤镜

这个滤镜的 operator 字段比较麻烦,它可以让两个输入图像逐像素在图像空间形成组合组合,feCompositeoverinatopout、xor、 lighterarithmetic 这些参数,这里我们使用 out 字段,out 字段定义图像显示的是由 in 属性定义的源图形中,位于 in2 的目标图形之外的部分。

<filter>
  <feComposite
    operator="out"
    in="OUTLINE_10"
    in2="SourceAlpha"
    result="OUTLINE_20"
  />
  <!-- 待填充 -->
</filter>

feTurbulence 滤镜

这个滤镜就很秀了,feTurbulence 滤镜也叫作湍流滤镜。在自然界中,很多物体,很多材质的纹理都表现为不规则,而我们代码实现的一般都是有规律的扁平效果,feTurbulence 就可以实现这种随机效果,他的参数可能第一眼看起来也有些麻烦,不过这里会具体介绍一下

  • baseFrequency 滤波器基元噪声函数的基频参数,改变它的值可以改变噪声的密集程度
  • type: 支持 fractalNoiseturbulence, 指示过滤器原语是否应该执行噪声或湍流功能,具体可以戳这里看一下实际效果
  • numOctaves:定义噪声的倍频,倍频的数量越多,噪声看起来越自然
  • seed:他可以改变噪声的形状
<feTurbulence
  baseFrequency=".05"
  type="fractalNoise"
  numOctaves="3"
  seed="0"
  result="Texture_10"
/>

如上我们就生成了一段这样的噪声

那么噪声该如何应用到过滤器中呢,就需要用到接下来的 feDisplacementMap 滤镜了

feDisplacementMap 滤镜

feDisplacementMap 实际上是一个位置替换滤镜,就是改变元素和图形的像素位置的。

feDisplacementMap 滤镜在业界的主流应用是对图形进行形变,扭曲,液化。他比较重要的是 scale 参数,scale 属性定义了滤镜上的置换缩放因子。scale 越大,则偏移越大。

现在我们将 feTurbulence 湍流滤镜和我们的文本结合,就会产生如下效果。

<feDisplacementMap
  scale="10"
  in="OUTLINE_20"
  in2="Texture_10"
  result="OUTLINE_30"
/>

feColorMatrix 滤镜

当谈到颜色的处理,feColorMatrix 是你最好的选择。feColorMatrix 是过滤中的一种类型,使用矩阵来影响颜色的每个通道(基于RGBA),你可以将其想象成 Photoshop 中通道编辑一样。

feColorMatrix 看起来像下方代码这样使用(原始图像RGBA的值默认为1)。矩阵计算 RGBA 每行的最终值,最后一个值是一个乘数。

<feColorMatrix 
  type="matrix"
  values="R 0 0 0 0,  
          0 G 0 0 0, 
          0 0 B 0 0, 
          0 0 0 A 0 "
/>
1 0 0 0 0 // R = 1*R + 0*G + 0*B + 0*A + 0 
0 1 0 0 0 // G = 0*R + 1*G + 0*B + 0*A + 0 
0 0 1 0 0 // B = 0*R + 0*G + 1*B + 0*A + 0 
0 0 0 1 0 // A = 0*R + 0*G + 0*B + 1*A + 0

这里我们定义一个改变着色的滤镜,再用 feComposite 融合到文案中,我们用到了 arithmetic 组合,不用也可以,这里只是为了效果。

  • arithmetic:该值表示属性中定义的源图形 in 和属性中定义的目标图形 in2 使用以下公式进行组合:result = k1 * in1 * in2 + k2 * in1 + k3 * in2 + k4
<feColorMatrix
  type="matrix"
  values="20 0 0 0 0,
  0 20 0 0 0,
  0 0 20 0 0,
  0 0 0 1 0"
  in="Texture_10"
  result="Texture_20"
/>
<feComposite
  operator="arithmetic"
  k2="-1"
  k3="1"
  in="Texture_20"
  in2="OUTLINE_30"
  result="OUTLINE_40"
/>

step2:绘制蓝色内容

feOffset 滤镜

feOffset 过滤器原语允许偏移输入图像。属性 dx 和属性 dy 的值指定了它的偏移量。

这里我们混入偏移量之后再给内容加上噪音。

<feOffset dx="-3" dy="4" in="SourceAlpha" result="FILL_10" />
<feDisplacementMap
  scale="17"
  in="FILL_10"
  in2="Texture_10"
  result="FILL_20"
/>

feFlood 滤镜

该滤镜用 flood-color 元素定义颜色,用 flood-opacity 元素定义不透明度,通过这两个参数填充了滤镜子区域。

然后我们继续通过 feComposite 将准备好的颜色滤镜与文本合并,这里我们使用 in 参数

  • in : 由 in 属性定义的源图形部分与在 in2 属性中定义的目标图形重叠,替换目标图形。
<feFlood
  flood-color="#73DCFF"
  flood-opacity="0.75"
  result="COLOR-blu"
/>
<feComposite
  operator="in"
  in="COLOR-blu"
  in2="FILL_20"
  result="FILL_50"
/>

最后我们想让 蓝色的内部文字与黑色的边框合并,这里我们就可以用到 feMerge 参数了

feMerge 滤镜

feMerge滤镜允许同时应用滤镜效果而不需要考虑顺序。

<feMerge result="merge2">
  <feMergeNode in="FILL_50" />
  <feMergeNode in="OUTLINE_40" />
</feMerge>

step3: 添加上阴影

feConvolveMatrix 滤镜

这个过滤器原语是最强大和最难掌握的过滤器之一。它的主要目的是使您能够创建自己的过滤器。简而言之,您将定义一个像素栅格(内核矩阵),它会根据其相邻像素的值来改变像素。这样就可以创建自己的滤镜效果,例如模糊或锐化滤镜,或者创建挤压。

下面是 feConvolveMatrix 创建一个 45 度、8 像素深度的挤压。该 order 属性定义了宽度和高度,以便基元知道是应用 8×8 矩阵:

<feConvolveMatrix
  order="8,8"
  divisor="1"
  kernelMatrix="
  1 0 0 0 0 0 0 0 
  0 1 0 0 0 0 0 0 
  0 0 1 0 0 0 0 0 
  0 0 0 1 0 0 0 0 
  0 0 0 0 1 0 0 0 
  0 0 0 0 0 1 0 0 
  0 0 0 0 0 0 1 0 
  0 0 0 0 0 0 0 1 "
  in="SourceAlpha"
  result="BEVEL_10"
/>
<feMorphology
  operator="dilate"
  radius="10"
  in="BEVEL_10"
  result="BEVEL_20"
/>

这样挤压我们可能看不清效果,我们还需要通过 feCompositeout 属性来取出挤压值。

<feComposite
  operator="out"
  in="BEVEL_20"
  in2="BEVEL_10"
  result="BEVEL_30"
/>
<feDisplacementMap
  scale="7"
  in="BEVEL_30"
  in2="Texture_10"
  result="BEVEL_40"
/>

因为我们希望它只拉伸到右侧和底部,所以我们必须偏移结果。

由于到了这里 IE 的兼容性有问题,所以为了保持跨浏览器的兼容性,我们将使用 feOffset 滤镜来处理偏移。

<feOffset dx="-7" dy="-7" in="BEVEL_40" result="BEVEL_60" />
<feComposite
  operator="out"
  in="BEVEL_60"
  in2="OUTLINE_10"
  result="BEVEL_70"
/>

然后我们再通过 feMerge 将以上的滤镜做合并。

<feMerge result="merge2">
  <feMergeNode in="BEVEL_70" />
  <feMergeNode in="FILL_50" />
  <feMergeNode in="OUTLINE_40" />
</feMerge>

到此主要的滤镜就用完了,接下来我们只是为了打到效果重复使用滤镜即可。

step4:完成剩余配置

<feOffset dx="-9" dy="-9" in="BEVEL_10" result="BEVEL-FILL_10" />
  <feComposite
    operator="out"
    in="BEVEL-FILL_10"
    in2="OUTLINE_10"
    result="BEVEL-FILL_20"
  />
  <feDisplacementMap
    scale="17"
    in="BEVEL-FILL_20"
    in2="Texture_10"
    result="BEVEL-FILL_30"
  />
  <feComposite
    operator="in"
    in="COLOR-red"
    in2="BEVEL-FILL_30"
    result="BEVEL-FILL_50"
  />

最后

最后我们把所有的滤镜合并,再加上一个背景色,就可以实现上文说的效果,是不是很简单。

<feMerge result="merge2">
    <feMergeNode in="BEVEL-FILL_50" />
    <feMergeNode in="BEVEL_70" />
    <feMergeNode in="FILL_50" />
    <feMergeNode in="OUTLINE_40" />
</feMerge>

如上就是所有步骤啦,全部代码都在这里,如果哪里看不懂可以再放 git 地址。 SVG 的过滤器其实十分强大,这里只是一个入门级教程,对此十分感兴趣可以查阅 MDN 教程。

参考:

  1. apike.ca/prog_svg_fi…
  2. css-tricks.com/look-svg-li…
  3. www.creativebloq.com/netmag/how-…