算法与数据结构 / 动画引擎原理 · 从插值到播放控制 / SVG 滤镜动画:水波、溶解、发光 待审核 41 / 47
feTurbulence · feDisplacementMap · feGaussianBlur

SVG 滤镜动画:水波、溶解、发光

补间的「值」并不限于几何坐标。一个 SVG filter primitive 的属性——feTurbulencebaseFrequencyfeDisplacementMapscalefeGaussianBlurstdDeviation——同样是标量。把它接到引擎的 onUpdate 上逐帧 setAttribute,就得到了程序化的表面质感:水面波动、图形溶解、光晕呼吸。

引擎不关心这个数最终去往坐标还是滤镜,它只负责把一个数从 A 平滑推到 B。

1 · 三种质感的共同出口

图 1-1 · 三个 filter primitive 各被一条补间驱动。可切换效果、强度与周期,读数给出当前被写入的属性名与值。

三个效果的差别不在动画,而在出口与重复方向。发光呼吸把 stdDeviation 在 0 与峰值之间来回,用 repeatType: 'mirror' 让去程与回程的缓动曲线关于中点对称;溶解让 scale 从 0 单向拉大,图形被噪声场逐渐撕开,用 reverse 收回。

2 · 滤镜链的顺序决定语义

这三个 primitive 串在同一条 filter 里,顺序是模糊、噪声、置换。feDisplacementMap 拿前面的模糊结果当 in、噪声当 in2,按噪声的 R、G 通道去偏移每个像素。所以水波效果里改的虽是 baseFrequency,看到的却是置换的强弱变化——噪声的空间频率变了,被它推开的像素图案随之变。

注 · feTurbulence 的输出是一张伪随机噪声图,seed 固定时它完全确定,与时间无关。本页看到的「流动」是 baseFrequency 在变造成的,噪声图整体在被拉伸与压缩,而不是真的在平移。要做真正的定向流动,得动 feOffset 或给 feTurbulence 加一个随时间平移的坐标系——单靠改频率,图案会在原地「呼吸」而非「流走」。

警示 · 滤镜是逐像素的,开销与被滤区域的面积成正比,且每帧重算。本页的 filter 把区域扩到 180%(模糊与置换都会溢出原始边界,不扩会被裁掉),意味着实际重算面积是元素的三倍多。这类动画放在合成层上也省不掉——合成器那页讲的 transform / opacity 走 GPU 快通道,filter 不在其列。

const blur = svg.querySelector('#blur');

new Animation({
  keyframes: [0, 12],
  duration: 2000,
  repeat: Infinity,
  repeatType: 'mirror',
  onUpdate: (v) => {
    blur.setAttribute('stdDeviation', v);
  },
});

把进度接到 stroke-dashoffset 得到线条自绘,见 SVG 描边;接到 viewBox 得到摄像机,见 viewBox 摄像机。三者共享同一条「值随时间」的插值曲线,只是把输出端换了一个属性。