图片渐变映射是将图像像素亮度或Alpha通道值映射为渐变色,需用Canvas手动读取像素、计算灰度、插值渐变色并重绘,核心步骤包括drawImage、getImageData、灰度加权计算、归一化插值及putImageData。

什么是图片渐变映射,不是 CSS 渐变背景
很多人搜“图片渐变映射”,其实是想把一张图片的**像素亮度或 Alpha 通道**,映射成某种线性/径向渐变颜色(比如:暗部变蓝、亮部变黄),而不是给图片加个渐变遮罩层。HTML5 原生不提供“图像 → 渐变色”这种映射函数,必须靠 CanvasRenderingContext2D 手动读取像素、计算映射、重绘。
用 getImageData + putImageData 实现灰度→渐变映射
核心思路:把原图转为灰度值(0–255),再用这个值查表或插值到一个预设渐变色数组中。注意不能直接对 img 元素操作,必须绘制到 后处理。
- 图片需同源(否则
getImageData抛SecurityError) - 先调用
ctx.drawImage(img, 0, 0)将图片画入 canvas - 用
ctx.getImageData(0, 0, width, height)获取像素数组(Uint8ClampedArray,每 4 个元素一组:R/G/B/A) - 遍历每个像素,算灰度:
gray = 0.299*r + 0.587*g + 0.114*b(YUV 加权) - 将
gray归一化为t = gray / 255,再插值到你的渐变色断点(例如[[0,0,255], [255,255,0]]表示蓝→黄) - 写回
data数组,并用ctx.putImageData()刷新画面
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = () => {
canvas.width = img.width; canvas.height = img.height;
ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const gradientColors = [[0,0,255], [255,255,0]]; // 蓝→黄
for (let i = 0; i < data.length; i += 4) {
const r = data[i], g = data[i+1], b = data[i+2];
const gray = 0.299*r + 0.587*g + 0.114*b;
const t = Math.min(1, Math.max(0, gray / 255));
const [r0,g0,b0] = gradientColors[0];
const [r1,g1,b1] = gradientColors[1];
data[i] = r0 + t*(r1-r0);
data[i+1] = g0 + t*(g1-g0);
data[i+2] = b0 + t*(b1-b0);
}
ctx.putImageData(imageData, 0, 0);
};
img.src = 'photo.jpg';
createRadialGradient 和 createLinearGradient 不适用此场景
这两个 Canvas API 是用来**绘制渐变形状**的(比如画一个渐变圆),它们生成的是 CanvasGradient 对象,无法反向作用于已有图像像素。强行用 ctx.fillStyle = grad 再 fillRect 只会盖住原图,不是“映射”。真要结合使用,得自己做逐像素采样:用坐标 (x,y) 算出该点在渐变中的归一化位置 t,再查色——但这已经脱离“图片映射”本意,变成自定义着色器逻辑了。
性能与兼容性要注意这些点
大图(如 2000×3000)做全量像素遍历会卡顿,尤其低端设备。实际项目中建议:
立即学习“前端免费学习笔记(深入)”;
- 用
requestIdleCallback分块处理,避免阻塞主线程 - 对高清图先缩放到 50% 再处理(
canvas.width/height设小些),视觉损失小但性能提升明显 - IE 完全不支持
getImageData的跨域图片;Safari 对data:协议图片有限制 - 若需实时响应(如拖拽调节映射曲线),考虑用 WebGL(
WebGLRenderingContext)替代 Canvas 2D,但复杂度陡增
真正难的不是写几行代码,而是决定灰度怎么转色——加 gamma 校正?用 Lab 色彩空间?是否保留原始色相?这些没标准答案,得看设计目标。










