SVG 滤镜

SVG 滤镜是什么

SVG 滤镜是用来为 SVG 中的形状或图形创建复杂效果的一种机制,SVG 提供了以下滤镜可供使用:

  1. <feBlend>:用于将两个图像按照指定的混合模式进行混合。
  2. <feColorMatrix>:用于通过矩阵操作调整图像的颜色和透明度。
  3. <feComponentTransfer>:用于对图像的 RGBA 通道进行独立的颜色处理和调整。
  4. <feComposite>:用于将多个图像按照指定的合成操作进行合成。
  5. <feConvolveMatrix>:用于应用卷积操作,可以实现模糊、锐化等效果。
  6. <feDiffuseLighting>:用于根据光源的位置和属性计算图像的表面照明效果。
  7. <feDisplacementMap>:用于根据位图图像的像素值来扭曲原始图像,创建位移效果。
  8. <feDropShadow>:用于在图像或文本周围添加投影阴影效果。
  9. <feFlood>:用于创建一个填充整个图像区域的颜色或渐变。
  10. <feGaussianBlur>:用于将图像进行高斯模糊处理。
  11. <feImage>:用于在图像中插入外部的位图图像。
  12. <feMerge>:用于将多个图像按照指定的顺序进行合并。
  13. <feMorphology>:用于对图像进行形态学处理,如膨胀或腐蚀。
  14. <feOffset>:用于将图像进行平移偏移。
  15. <feSpecularLighting>:用于根据光源的位置和属性计算图像的镜面高光效果。
  16. <feTile>:用于将图像平铺到指定的区域内。
  17. <feTurbulence>:用于创建具有噪点或纹理效果的图像。

SVG 滤镜定义在 <filter> 标签内,并且可以使用 id 属性来为其指定一个 ID:

<svg xmlns="http://www.w3.org/2000/svg">  
    <filter id="filter-name">  
        <!-- 在这里定义 SVG 滤镜... -->
    </filter>  
</svg>

例如,这是一个定义了 <feGaussianBlur> 滤镜的 SVG 滤镜组,并且为其指定了 blur 这个 ID。

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <defs>
    <filter id="blur">
      <feGaussianBlur stdDeviation="18" />
    </filter>
  </defs>
</svg>

至于为什么这里要特别提到 <filter> 标签支持 id 属性,继续往下阅读你就知道啦~

已经有 CSS 滤镜了,要这 SVG 滤镜有什么用呢

相比于 CSS 滤镜,SVG 滤镜可以实现一些 CSS 滤镜难以实现的效果,例如 CSS 滤镜中的 blur() 只能指定一个参数,用来控制整体的模糊程度,但是不能分别设定在 x 轴和 y 轴上的模糊程度。而 SVG 中的 <feGaussianBlur> 滤镜则可以分别设定在 x 轴和 y 轴上的模糊程度,<feGaussianBlur> 滤镜接受一个 stdDeviation 参数,如果只为这个参数设定单个数值,则和 CSS 中的 blur() 滤镜效果是一样的;如果为这个参数设定两个数值,例如 <feGaussianBlur stdDeviation="10 0" />,则前面的值表示在 x 轴上的模糊程度,后面的值表示在 y 轴上的模糊程度。效果如下:

See the Pen feGaussianBlur by LGiki (@lgiki-the-bold) on CodePen.

另外,SVG 的滤镜还支持 inresult 属性,可以将多个不同滤镜串联起来使用,做出很多 CSS 滤镜难以做出来的效果,并且 in 属性还支持输入不同类型的值,例如设定为 SourceGraphic 表示将图像自身作为滤镜的输入、设定为 SourceAlpha 表示将元素的透明度作为滤镜的输入等…

可以说,SVG 的滤镜就像 Photoshop 的图层混合模式,可以随意搭配组合,构建出各种奇特的效果。

可是…CSS 滤镜可以直接运用于元素上,SVG 滤镜可以吗?

当然可以!前面有提到 SVG 的 <filter> 标签可以设定 id 属性,有了 id 之后只需在 CSS 中为元素增加 filter: url('#filter-name') 就可以将 SVG 滤镜应用于任意 HTML 元素上了,例如这样:

.selector {
	filter: url('#filter-name');
	
	/* 另外,也可以从外部 SVG 文件中加载 filter: */
	filter: url('svg_file.svg#filter-name');
}

这就是 SVG 滤镜好用的地方了,可以运用在任意的 HTML 元素上,例如网页上的文字、图片、视频等各种元素,可以实现很多奇特、有趣的效果,例如接下来要介绍的 Gooey Effect。

另外,在 https://caniuse.com/svg-filtershttps://caniuse.com/css-filters 的对比中可以看到,各家浏览器对 SVG 滤镜对支持其实是比 CSS 滤镜更好的。

Gooey Effect

先来看看 Gooey Effect 长什么样子:(请使用 Firefox 或 Chrome 浏览器打开,请勿使用 Safari 浏览器)

See the Pen Gooey Effect by LGiki (@lgiki-the-bold) on CodePen.

我一开始是在 https://css-tricks.com/gooey-effect/ 发现 Gooey Effect 的,第一次看到这个效果的时候觉得很震撼,在看代码之前完全想不懂是如何实现的。看完之后才知道,原来通过 SVG 滤镜就能实现这么有趣的效果,这也是我开始研究 SVG 滤镜并写下这篇文章的原因。

如果想详细了解如何使用 SVG 滤镜实现 Gooey Effect 可以点击原文链接查看,下面我简单介绍一下这个效果的实现。

首先,从代码里面可以看到,这个特效本质上是通过 <feGaussianBlur><feColorMatrix><feBlend> 这三个滤镜实现的,那么就分别来看看这三个滤镜。

<feGaussianBlur> 滤镜

前文有提到,<feGaussianBlur> 滤镜就是用来实现高斯模糊的滤镜,接受一个 stdDeviation 参数,表示模糊的程度,值越大,模糊的程度越大,反之则越小,并且支持分开设定图形在 x 轴和 y 轴上的模糊程度。高斯模糊效果在很多软件中都有应用,例如 macOS、iOS 在很多 UI 的设计上就大量使用了高斯模糊的元素,Windows 10、Windows 11 中也有一定的运用。

而当在两个互相接近的移动图形上叠加一个高斯模糊滤镜之后,在图形接近/分离的时候,边缘会出现类似于互相吸附的效果:(你可以点击 blur 选框来切换是否模糊)

See the Pen Gooey-Effect-Step-1 by LGiki (@lgiki-the-bold) on CodePen.

这就是 Gooey Effect 的关键,通过高斯模糊制造出两个图形互相吸附的效果。那么,接下来的问题就是,如何保留这种图形互相吸附的效果,同时让图形的边缘清晰。仔细观察这个高斯模糊之后的图形,如果可以把图形边缘模糊的像素映射为黑色似乎就能解决这个问题,于是就可以使用 <feColorMatrix> 滤镜来实现这件事情。

<feColorMatrix> 滤镜

SVG 中的 <feColorMatrix> 滤镜可以基于一个转换矩阵对元素的颜色进行转换,可以对元素的 RGBA 四个通道的颜色进行转换,其中,转换矩阵是一个 4 行 5 列的矩阵,定义如下:

$$ \begin{bmatrix} r1 & r2 & r3 & r4 & r5\\ g1 & g2 & g3 & g4 & g5\\ b1 & b2 & b3 & b4 & b5\\ a1 & a2 & a3 & a4 & a5 \end{bmatrix} $$

假设用 $R^\prime$、$G^\prime$、$B^\prime$、$A^\prime$ 分别表示经过 <feColorMatrix> 滤镜后每个像素点的 RGBA 值,那么 <feColorMatrix> 的运算实际上就是一个矩阵乘法的过程:

$$ \begin{bmatrix} R^\prime \\ G^\prime \\ B^\prime \\ A^\prime \\ 1 \end{bmatrix} = \begin{bmatrix} r_1 & r_2 & r_3 & r_4 & r_5\\ g_1 & g_2 & g_3 & g_4 & g_5\\ b_1 & b_2 & b_3 & b_4 & b_5\\ a_1 & a_2 & a_3 & a_4 & a_5\\ 0 & 0 & 0 & 0 & 1 \end{bmatrix} \cdot \begin{bmatrix} R \\ G \\ B \\ A \\ 1 \end{bmatrix} = \begin{bmatrix} r_1*R+r_2*G+r_3*B+r_4*A+r_5 \\ g_1*R+g_2*G+g_3*B+g_4*A+g_5 \\ b_1*R+b_2*G+b_3*B+b_4*A+b_5 \\ a_1*R+a_2*G+a_3*B+a_4*A+a_5 \\ 1 \end{bmatrix} $$

假设 <feColorMatrix> 的变换矩阵为:

$$ \begin{bmatrix} 1 & 0 & 0 & 0 & 0\\ 0 & 1 & 0 & 0 & 0\\ 0 & 0 & 2 & 0 & 0\\ 0 & 0 & 0 & 1 & 0 \end{bmatrix} $$

那么实际上做的运算就是:

$$ \begin{bmatrix} R^\prime \\ G^\prime \\ B^\prime \\ A^\prime \\ 1 \end{bmatrix} = \begin{bmatrix} 1 & 0 & 0 & 0 & 0\\ 0 & 1 & 0 & 0 & 0\\ 0 & 0 & 2 & 0 & 0\\ 0 & 0 & 0 & 1 & 0\\ 0 & 0 & 0 & 0 & 1 \end{bmatrix} \cdot \begin{bmatrix} R \\ G \\ B \\ A \\ 1 \end{bmatrix} = \begin{bmatrix} R \\ G \\ 2*B \\ A \\ 1 \end{bmatrix} $$

本质上就是把原本蓝色通道的值调整为原来的 2 倍,其余通道保持不变,那么图片应该就会变得更显蓝色一些。

我写了一个 feColorMatrix 的 Playground,你可以在线调节转换矩阵的值来看看对图片颜色的影响:https://fecolormatrix-playground.vercel.app/

好了,回到 Gooey Effect,现在要做的事就是把图形边缘变得清晰,把模糊部分的像素映射为黑色即可。

那些模糊的像素,其实就是 Alpha 通道,因此,可以通过 <feColorMatrix> 滤镜把 Alpha 通道的像素映射为

首先需要了解一下 Alpha 通道,假设背景色为白色(#ffffff)、前景色为黑色(#000000),那么在不同 Alpha 值下的颜色如下所示,从左到右分别是 Alpha 为 0 到 Alpha 为 255 的颜色:

0 127 255

从上文经过 <feGaussianBlur> 滤镜之后得到的图形来看,边缘模糊的像素本质上是透明度(Alpha 值)各不相同的像素,因此如果我们能把边缘那些半透明的像素变为纯色的像素,不就可以让图形的边缘清晰起来,同时又具有吸附效果了吗!这时候就可以通过 <feColorMatrix> 滤镜实现。

原文中给出的 <feColorMatrix> 滤镜的参数是:

$$ \begin{bmatrix} 1 & 0 & 0 & 0 & 0\\ 0 & 1 & 0 & 0 & 0\\ 0 & 0 & 1 & 0 & 0\\ 0 & 0 & 0 & 18 & -7 \end{bmatrix} $$

即将 Alpha 通道扩大到原来的 18 倍再减去 7:

$$ \begin{bmatrix} R^\prime \\ G^\prime \\ B^\prime \\ A^\prime \\ 1 \end{bmatrix} = \begin{bmatrix} 1 & 0 & 0 & 0 & 0\\ 0 & 1 & 0 & 0 & 0\\ 0 & 0 & 1 & 0 & 0\\ 0 & 0 & 0 & 18 & -7\\ 0 & 0 & 0 & 0 & 1 \end{bmatrix} \cdot \begin{bmatrix} R \\ G \\ B \\ A \\ 1 \end{bmatrix} = \begin{bmatrix} R \\ G \\ B \\ 18*A-7 \\ 1 \end{bmatrix} $$

这里有一个简单的示例程序,你可以拖动顶部的拖动条来控制 Alpha to Alpha(即 feColorMatrix 的第 4 行第 4 列,默认为 18)和 Alpha Offset(即 feColorMatrix 的第 4 行第 5 列,默认为 -7)的值:

See the Pen Gooey Effect with Controller by LGiki (@lgiki-the-bold) on CodePen.

你可以试着先将 Alpha Offset 的值调整为 0,然后拖动 Alpha to Alpha 看看随着 Alpha 通道的系数变大或变小,图像会出现什么样的变化,等固定在某个值之后,再试着拖动 Alpha Offset 看看图像又会出现什么变化,就能理解这个过程了。

<feBlend> 滤镜

<feBlend> 滤镜顾名思义就是将两个元素按照特定的混合模式混合,支持三个参数:inin2mode,其中,inin2 就是输入的两个图形,mode 即对这两个图形的混合模式,mode 的默认值是 normal,即这两张图正常叠加,in 显示在 in2 的顶层。

References