HTML5 Canvas 实现双重曝光滤镜应优先用 globalCompositeOperation = "lighten" 模式模拟胶片亮部优先叠加,需确保图片加载完成、尺寸对齐及跨域配置;不支持时降级为 "screen" 或 "overlay";加权混合需手动 getImageData 逐像素插值;暗部叠加可用 "multiply"。

HTML5 Canvas 实现真正的双重曝光滤镜,不能靠 CSS filter 或 SVG 滤镜,必须用像素级混合算法——核心是模拟胶片叠加的“亮部优先”逻辑,即同一位置取两张图中更亮的像素值。Canvas 的 globalCompositeOperation = "lighten" 是最直接、性能最优的实现方式,但需严格控制加载顺序、尺寸对齐和跨域配置。
用 lightened 合成模式快速实现胶片式双重曝光
这是最接近传统暗房效果的方案,无需逐像素计算,浏览器原生支持,性能好、代码简洁:
- 确保两张图片都已完全加载(监听
img.onload),否则drawImage()会画出空白 - 将画布尺寸设为统一目标宽高(如 800×600),两张图都缩放到该尺寸再绘制,避免错位或裁切
- 第一张图用默认模式绘制:
ctx.drawImage(img1, 0, 0, canvas.width, canvas.height) - 立即设置合成模式:
ctx.globalCompositeOperation = "lighten"(注意:每次绘制前都要重设,它不自动保持) - 绘制第二张图:
ctx.drawImage(img2, 0, 0, canvas.width, canvas.height)
若需加权控制(如主图占70%,辅图占30%)
globalAlpha 只能全局调透明度,无法实现非线性亮度加权;真正可控的方式是手动读取像素并加权混合:
- 先用
ctx.getImageData(0, 0, w, h)分别获取两张图的像素数据(需同尺寸、同源或带crossOrigin="anonymous") - 创建新
Uint8ClampedArray存储结果,遍历每个像素点 - 对每个通道(R/G/B)按权重插值:
result = Math.min(255, Math.max(0, r1 * 0.7 + r2 * 0.3)) - Alpha 通道建议保持原值或统一设为 255,避免半透导致灰雾感
- 最后用
ctx.putImageData()一次性写回,避免边读边写引发错误
适配移动端与降级处理
iOS Safari 在 15.4 之前不支持 "lighten",会静默回退为 "source-over",导致效果完全失效:
立即学习“前端免费学习笔记(深入)”;
- 检测支持性:
const supportsLighten = ctx.globalCompositeOperation === "lighten"(在设置后立即检查) - 不支持时可降级为
"screen"(发白感更强)或"overlay"(保留更多对比) - 更稳妥的 fallback 是预生成一张服务端合成图,或提示用户升级浏览器
- 所有图片务必添加
crossOrigin="anonymous",否则 getImageData 会触发 SecurityError
进阶:做暗部叠加(反向双重曝光)
如果想突出纹理、阴影或营造复古胶片感,"multiply" 更合适:
- 原理是各通道相乘再归一化:
result = (r1 / 255) × (r2 / 255) × 255 - 纯白区域(255)在 multiply 中不改变另一图,所以底图尽量避开大面积纯白
- 纯黑(0)会使结果全黑,因此辅图若有重要暗部细节,建议先轻微提亮
- 使用方式与 lighten 完全一致:先绘底图,再设
globalCompositeOperation = "multiply",再绘上层图



















