跳转到主要内容
返回文章
1 分钟阅读

用 SVG 滤镜做「沙子溶解」转场:feTurbulence + feDisplacementMap 实战

#SVG#动效#滤镜
用 SVG 滤镜做「沙子溶解」转场:feTurbulence + feDisplacementMap 实战

有一种转场,第一次见到时很难忘记:图片不是淡入淡出,而是像被风吹散的沙粒,一点一点消解、重组。我第一次是在一个博物馆项目里做出来的,它成了整个页面最抓眼球的细节。

它的实现完全依赖 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>

每一段的作用:

  1. feTurbulence 生成一张随机噪声图,这是「沙子」的纹理来源。
  2. feDisplacementMap 用噪声图去扰动图片,scale 越大,图片被撕扯得越碎。
  3. feOffset 让碎片在垂直方向位移,制造下落感。
  4. feGaussianBlur 加模糊,模拟远景的沙尘。
  5. 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 还能衍生出烟雾、火焰、噪点玻璃等无数可能。

约束本身,往往是创意的起点。

假作真时真亦假,无为有处有还无。

凯 · KAI000%
泪蛋蛋掉在酒杯杯里
DJ版 · 镐天
0:000:00