Canvas 2D 实现色彩分离需用 getImageData + putImageData 对 R/G/B 通道分别偏移整数像素并边界检查;WebGL 方案更高效,须在 Fragment Shader 中对纹理三次采样并归一化偏移量。

Canvas 2D 上下文里用 getImageData + putImageData 实现色彩分离
HTML5 本身没有内置“色彩分离”滤镜,所谓“HTML5 滤镜”实际依赖 Canvas 的像素级操作。核心是把 R、G、B 通道分别偏移不同像素,再合成——不是调色,而是错位。
常见错误是直接改 CSS filter 或 SVG filter,它们不支持通道级位移;也有人误用 ctx.filter(仅支持模糊/亮度等基础效果,不支持分离)。
实操建议:
- 用
canvas.getContext('2d')获取上下文,drawImage把图片画进去 - 调用
ctx.getImageData(0, 0, width, height)拿到原始像素数组data(Uint8ClampedArray,每 4 个值为 R/G/B/A) - 新建一个空
ImageData,遍历原数组,把 R 通道写入偏移(x + 2, y)位置,G 写入(x, y + 2),B 写入(x - 2, y + 1)—— 偏移量可调,但必须是整数像素 - 注意边界:写入时要判断目标坐标是否在画布范围内,否则会静默失败或报错
IndexSizeError - 最后用
ctx.putImageData()贴回画布
WebGL 方案更高效但需写 shader:用 gl_FragColor 分别采样偏移纹理
Canvas 2D 方案在大图或动画中容易卡顿,真正实用的色彩分离得上 WebGL。本质是 Fragment Shader 中对同一纹理做三次采样,R/G/B 各自加不同 vec2 偏移。
立即学习“前端免费学习笔记(深入)”;
关键点:
- 顶点着色器保持标准 UV 传递;片元着色器里用
texture2D(u_texture, v_uv + u_offsetR)取 R 分量,同理 G/B -
u_offsetR、u_offsetG、u_offsetB是 uniform,单位是纹理坐标(0.0–1.0),不是像素,需按画布宽高归一化 - 不要用
mix()简单叠加,要分别赋值:gl_FragColor = vec4(r, g, b, 1.0),否则通道会混合失真 - 若用 Three.js,可用
ShaderMaterial封装,但注意默认材质会启用 lighting,得设lights: false
CSS filter 和 做不了真分离,只能模拟色偏
有人试过 filter: sepia(1) hue-rotate(90deg) 或 SVG 的 ,这些只是线性变换 RGB 值,所有像素统一计算,不会产生通道错位效果——也就是没“分离感”。
典型表现:
- 图像整体变紫/变绿,但边缘依然锐利,没有 R/G/B 各自拖影的效果
- 无法控制每个通道的偏移方向和距离
-
feColorMatrix的 matrix 参数是 5×4 变换矩阵,只能缩放/平移颜色值,不能空间位移像素
移动端性能坑:iOS Safari 对 getImageData 有尺寸限制
iOS 15+ 的 Safari 在 canvas 宽高乘积超 ~1600万 像素时,getImageData 会直接返回空数据且不报错——这是静默降级,极易被忽略。
应对方式:
- 先用
canvas.width * canvas.height判断是否超限,超了就缩放图片再处理 - 避免在
requestAnimationFrame里反复调用getImageData,它触发强制同步读取,严重阻塞主线程 - Android Chrome 相对宽松,但低端机仍可能 OOM,建议最大处理尺寸控制在 1280×720 以内
色彩分离真正的复杂点不在算法,而在像素边界的越界检查、跨平台读取限制、以及 WebGL 下纹理坐标的归一化换算——这三处出错,效果就全歪了。










