SVG 滤镜动画:水波、溶解、发光
补间的「值」并不限于几何坐标。一个 SVG filter primitive 的属性——feTurbulence 的 baseFrequency、feDisplacementMap 的 scale、feGaussianBlur 的 stdDeviation——同样是标量。把它接到引擎的 onUpdate 上逐帧
setAttribute,就得到了程序化的表面质感:水面波动、图形溶解、光晕呼吸。
引擎不关心这个数最终去往坐标还是滤镜,它只负责把一个数从 A 平滑推到 B。
1 · 三种质感的共同出口
三个效果的差别不在动画,而在出口与重复方向。发光呼吸把 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 摄像机。三者共享同一条「值随时间」的插值曲线,只是把输出端换了一个属性。