HTML5 Canvas 实现动态波纹滤镜需用 requestAnimationFrame 驱动像素级 UV 偏移:预分配 createImageData 内存,按距离衰减正弦公式计算偏移量,结合鼠标事件触发扰动,并适配 devicePixelRatio。

HTML5 Canvas 如何用 requestAnimationFrame 实现动态波纹滤镜
纯 CSS 无法实现真正的动态波纹(如水波扩散、点击扰动),必须结合 Canvas + JavaScript 计算像素偏移。核心是用 getImageData 读取原图,对每个像素按正弦/衰减函数做 UV 偏移,再用 putImageData 渲染——关键在偏移量随时间变化。
常见错误是直接操作 DOM 图片元素加 filter: url(#wave),那只是 SVG 滤镜的静态定义,不支持时间变量;也有人误用 filter: blur() 或 hue-rotate(),这些和波纹无关。
- 波纹本质是「纹理坐标扰动」,不是颜色变换,必须走 Canvas 像素级操作
- 每次帧更新需重置偏移相位(如
time += 0.02),否则动画卡死 - 为避免性能爆炸,建议限制画布尺寸(如最大 800×600),或用
ctx.drawImage缩放原始图像后再处理
怎样用 createImageData 预分配内存避免频繁 GC
每帧都调 getImageData 会触发大量内存分配,导致卡顿。正确做法是提前用 createImageData 创建缓冲区,复用同一块内存:
const canvas = document.getElementById('wave-canvas');
const ctx = canvas.getContext('2d');
const width = canvas.width;
const height = canvas.height;
// 预分配一次
const imageData = ctx.createImageData(width, height);
const data = imageData.data; // 直接引用像素数组后续每一帧只需修改 data 数组值,再 ctx.putImageData(imageData, 0, 0) 输出,跳过重复创建开销。
立即学习“前端免费学习笔记(深入)”;
- 不要在动画循环里写
ctx.getImageData(0, 0, w, h)—— 这是典型性能雷区 -
data是长度为width × height × 4的 Uint8ClampedArray,索引i对应 RGBA 四字节,修改时注意边界(i+0是 R,i+1是 G…) - 若需叠加原图纹理,得先用
ctx.drawImage(img, 0, 0)绘制底图,再读取ctx.getImageData获取初始像素
波纹公式怎么写:用 Math.sin + 距离衰减模拟真实水波
简单正弦偏移(dx = Math.sin(x + time) * amp)看起来生硬。真实波纹需满足:中心扰动强、向外快速衰减、多频率叠加。推荐这个轻量公式:
const dx = Math.sin(dist * 0.05 - time * 2) * amp / (dist + 1); const dy = Math.cos(dist * 0.07 - time * 1.5) * amp / (dist + 1);
其中 dist = Math.sqrt((x - cx)**2 + (y - cy)**2) 是到波源点(如鼠标位置 cx, cy)的距离。
-
/ (dist + 1)是关键衰减项,没它波纹会平铺全图 - 两组不同频率(
0.05vs0.07)和相位(time*2vstime*1.5)叠加,避免单调感 - 鼠标点击触发时,把
cx, cy设为点击坐标,并重置time = 0,否则新波和旧波干涉混乱
为什么用 WebGL 更合适但多数人该先用 Canvas
Canvas 方案在 1080p 下帧率易掉到 30fps 以下,尤其多波源时。WebGL 理论上更优:用 fragment shader 直接计算偏移,GPU 并行处理百万像素。但代价是学习曲线陡峭、调试困难、移动端兼容性差(部分 Android WebView 不支持 WEBGL_depth_texture 扩展)。
除非你已在用 Three.js 或需要同时跑 5+ 动态波源,否则 Canvas + 上述优化已够用。真正容易被忽略的是:Canvas 波纹效果依赖设备像素比(window.devicePixelRatio),在 Retina 屏上若没缩放 canvas.style.width/height,图像会模糊——必须显式设置 canvas.width = width * dpr 并 ctx.scale(dpr, dpr)。










