Canvas 2D 手动加噪点最可控:需用 ImageData 逐像素叠加哈希式扰动(如 Math.sin),避免 Math.random() 导致假色;缓存原图数据、限制重绘区域、RGBA 分通道截断处理,禁用无效的 ctx.filter 或 CSS filter。

Canvas 2D 上用 ImageData 手动加噪点最可控
HTML5 本身没有内置“噪点滤镜”,filter: url(#noise) 或 CSS filter 都不支持真正随机噪点。唯一可靠路径是用 CanvasRenderingContext2D 拿到像素数据,逐点叠加伪随机灰度偏移。
关键不是“怎么写滤镜”,而是“怎么避免性能崩盘+视觉假色”:
- 别在每一帧都全量重绘:先缓存原图
ImageData,只对目标区域做噪点叠加 - 噪点强度用
Math.random() * intensity不够稳——换成Math.sin(x * 0.1 + y * 0.2) * 0.5 + 0.5这类哈希式扰动,避免相邻像素大片同值 - RGBA 四通道都要处理,但 Alpha 通常保持不变;若只动 RGB,记得用
data[i] = Math.min(255, Math.max(0, data[i] + offset))截断,否则溢出变黑块
ctx.filter 对噪点完全无效,别浪费时间试
Chrome/Firefox 的 ctx.filter 仅支持 CSS filter 列表(如 blur()、contrast()),不解析 SVG 滤镜里的 ,更不支持自定义噪声函数。你写 ctx.filter = "url(#myNoise)",控制台不会报错,但画面零反应。
常见误操作:
立即学习“前端免费学习笔记(深入)”;
- 把 SVG
定义塞进 HTML 里,以为 canvas 能自动识别——它不能 - 用
filter: url(#xxx)直接作用于元素——这只会让整个 canvas 被当做一个位图去套滤镜,噪点仍是静态纹理,无法随内容动态生成 - 试图用
WebGL的 fragment shader 写噪点却卡在矩阵传参上——小项目真没必要,Canvas 2D 足够
用 createPattern 快速铺静态噪点纹理(适合背景)
如果只要模拟胶片颗粒感或老电视雪花,并不需要每帧变化,可以绕过像素操作,用 Canvas 绘制一个 64×64 的噪点图案,再用 createPattern 平铺:
const noiseCanvas = document.createElement('canvas');
noiseCanvas.width = noiseCanvas.height = 64;
const noiseCtx = noiseCanvas.getContext('2d');
const noiseData = noiseCtx.createImageData(64, 64);
for (let i = 0; i < noiseData.data.length; i += 4) {
const v = Math.random() * 64;
noiseData.data[i] = v; // R
noiseData.data[i+1] = v; // G
noiseData.data[i+2] = v; // B
noiseData.data[i+3] = 255; // A
}
noiseCtx.putImageData(noiseData, 0, 0);
const pattern = noiseCtx.createPattern(noiseCanvas, 'repeat');之后在主 canvas 上 ctx.fillStyle = pattern 填充,或者用 globalCompositeOperation = 'overlay' 叠加到图像上。比逐像素快一个数量级,但注意:pattern 是静态的,缩放时会模糊,需配合 imageSmoothingEnabled = false。
WebGL 方案只在需要实时动态噪点时才值得上
比如做视频流实时处理、粒子系统叠加扰动,或要和其它 shader 效果联动。此时核心是 fragment shader 里用 fract(sin(dot(xy, vec2(12.9898,78.233))) * 43758.5453) 这类经典哈希函数生成伪随机值。
但代价明显:
- 必须管理
WebGLRenderingContext、着色器编译、buffer 绑定 - 移动端兼容性仍需检查
OES_texture_float扩展是否可用 - 单帧噪点强度调整不如 Canvas 2D 直观——得重新编译 shader 或传 uniform 变量
多数网页需求里,“Canvas 2D + 适度优化的伪随机”已经覆盖 95% 场景。真正卡住的往往不是算法,而是忘了调 ctx.imageSmoothingEnabled = false 导致噪点图案被抗锯齿糊成一片灰。










