1 分钟阅读
用 SVG 滤镜做「沙子溶解」转场:feTurbulence + feDisplacementMap 实战
#SVG#动效#滤镜
有一种转场,第一次见到时很难忘记:图片不是淡入淡出,而是像被风吹散的沙粒,一点一点消解、重组。我第一次是在一个博物馆项目里做出来的,它成了整个页面最抓眼球的细节。
它的实现完全依赖 SVG 滤镜,不需要任何图片素材。
滤镜链:五段式
核心是这一串滤镜,它们依次对图片进行处理:
<filter id="sand">
<feTurbulence type="fractalNoise" baseFrequency="1.8" numOctaves="4" result="noise" />
<feDisplacementMap in="SourceGraphic" in2="noise" scale="0"
xChannelSelector="R" yChannelSelector="G" />
<feOffset dx="0" dy="0" />
<feGaussianBlur stdDeviation="0" />
<feColorMatrix type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" />
</filter>每一段的作用:
feTurbulence生成一张随机噪声图,这是「沙子」的纹理来源。feDisplacementMap用噪声图去扰动图片,scale越大,图片被撕扯得越碎。feOffset让碎片在垂直方向位移,制造下落感。feGaussianBlur加模糊,模拟远景的沙尘。feColorMatrix控制整体透明度,让图片最终归于透明。
用 rAF 驱动
动画的本质,是在一帧帧地改变这几个参数。我用 requestAnimationFrame 在 900ms 内插值:
const tick = (now: number) => {
const t = Math.min((now - start) / duration, 1);
const e = 1 - Math.pow(1 - t, 4); // quartic ease-out
applyFilter(150 * (1 - e), -80 * (1 - e), 6 * (1 - e), t);
if (t < 1) requestAnimationFrame(tick);
};进场时,scale 从 150 衰减到 0,图片从「碎沙」凝聚成完整图像;出场则反向。
一个容易被忽略的坑
在服务端渲染的场景里,滤镜的 id 如果用了随机数,会在 hydration 时对不上。稳妥的做法是用 React 的 useId,并去掉它返回结果里的冒号:
const rawId = useId();
const filterId = `sand${rawId.replace(/[^a-zA-Z0-9]/g, '')}`;结语
SVG 滤镜是一片被低估的富矿——它不需要素材、体积趋近于零,却能做出远超预期的质感。沙子溶解只是其中一种,feTurbulence 还能衍生出烟雾、火焰、噪点玻璃等无数可能。
约束本身,往往是创意的起点。