canvas动态流体效果本质是像素邻域的扩散+衰减+柔化,非真实物理模拟;通过离屏canvas逐帧操作rgba值,仅处理活跃像素,结合方向权重、通道差异化衰减与扰动位移提升视觉质感。

Canvas 绘制动态流体效果若基于像素邻域计算,本质不是模拟真实流体物理,而是用图像空间的局部扩散与叠加逻辑,营造“颜料晕染”“墨迹渗开”这类视觉流动感。它绕开了粒子系统与速度场的复杂更新,直接在像素层面操作,适合轻量、静态触发或背景型动画,且兼容性好、代码路径清晰。
核心思路:扩散 + 衰减 + 柔化
每帧对离屏 canvas 的像素数据做三件事:把当前有颜色的像素向周围邻域“分发”一部分亮度/色彩值;自身像素强度按比例衰减;最后加轻微模糊增强边缘自然感。这不是卷积滤波,而是带方向偏好的逐像素传播。
- 邻域选 4 方向(上、下、左、右)或 8 方向,权重可设为均匀(各 0.15)或中心偏重(如中心保留 0.4,四邻各 0.15)
- 只对 alpha > 30 的像素执行扩散,避免干扰背景
- 扩散时用 加法叠加(而非替换),即 targetPixel += sourcePixel × weight,需限制 R/G/B 不超过 255
- 衰减统一乘以 0.98~0.995,防止颜色过快消失
关键实现步骤
必须用离屏 canvas 隔离读写,否则 getImageData 和 putImageData 会因原地修改导致脏读:
- 准备两个 canvas:main(显示用)、offscreen(运算用),尺寸一致
- 初始颜料区域用 drawImage 或 fillRect 绘制到 offscreen 上(建议用半透明径向渐变模拟滴落起点)
- 每帧:先 getImageData → 遍历 data 数组 → 对每个非透明像素,读取其 RGBA,按权重写入相邻位置 → 新数组存结果 → putImageData 回 offscreen → 最后 drawImage 到 main
- 扩散后可追加 ctx.filter = "blur(0.3px)"(仅影响绘制,不影响 data),让边缘更柔和
性能与边界处理要点
邻域计算易卡顿,尤其大图。控制开销的核心是“不做全图遍历”:
- 维护一个“活跃像素坐标集”,只遍历上一帧中 alpha > 0 的像素及其邻域,跳过大片空白区
- 限制最大扩散帧数(如 25 帧),结束后停止更新,避免持续占用 CPU
- 宽度超过 800px 的图建议降采样处理(如 0.5 倍缩放后再运算,完成再放大绘制)
- 避免在 requestAnimationFrame 中频繁调用 getImageData —— 它是同步阻塞操作,一次就够了
增强表现力的小技巧
纯均质扩散看起来像雾气,加入变化才能接近“流体”质感:
- 扩散权重随 y 坐标增大而略增(模拟重力下沉),例如 weightDown *= 1.2
- 对 R/G/B 分通道设置不同衰减系数(如蓝色衰减慢、红色快),制造色彩分离感
- 在扩散前,对像素做轻微位移:x += Math.sin(y * 0.02) * 0.5,引入细微扰动
- 启用 globalCompositeOperation = "lighter" 绘制最终结果,让多层扩散自然叠加发光











