Canvas 图像绘制
一、图像绘制
drawImage() 用于图像绘制,有三种调用方式:
drawImage(image, dx, dy)drawImage(image, dx, dy, dWidth, dHeight)drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
参数:
image:图像源,例如:HTMLImageElement、SVGImageElement、HTMLVideoElement、HTMLCanvasElement、ImageBitmap、OffscreenCanvas、VideoFrame;sx(可选):开始剪切的 x 坐标位置;sy可选:开始剪切的 y 坐标位置;sWidth:可选,被剪切图像的宽度;sHeight:可选,被剪切图像的高度;dx:在画布上放置图像的 X 轴坐标;dy:在画布上放置图像的 Y 轴坐标;dWidth:所绘制图像的宽度(伸展或缩小图像)dHeight:所绘制图像的高度(伸展或缩小图像)
示例:
// HTML
<canvas id="canvas" width="200" height="150" style="border: 1px dashed gray;"></canvas>
<img id="image" src="./image.png" alt="image" />
// JS
const cvs = document.getElementById('canvas')
const ctx = cvs.getContext('2d')
const image = document.getElementById("image");
image.addEventListener('load', (e) => {
// 将图像绘制到画布的 (50, 0) 坐标处
ctx.beginPath();
ctx.drawImage(image, 50, 0);
// 将图像绘制到画布的 (50, 60) 坐标处
// 并将其缩放为宽 100、高 30 的图像
ctx.beginPath();
ctx.drawImage(image, 50, 60, 100, 30);
// 原图像从坐标 (10,20) 处
// 截取一个宽为 36 高为 37 的图像
// 并将其绘制到画布的 (50, 100) 坐标处
// 再将其缩放为宽 51、高 52 的图像
ctx.beginPath();
ctx.drawImage(image, 10, 20, 36, 37, 50, 100, 51, 52);
});
效果如下:

二、图像平铺
ctx.createPattern(image, repetition) 用于在指定的方向内重复图像。
参数:
image:图片、画布或视频元素;repetition:指定如何重复图像,可选值如下:repeat(默认):在水平和垂直方向重复;repeat-x:只在水平方向重复;repeat-y:只在垂直方向重复;no-repeat:只显示一次(不重复)
示例:
image.addEventListener('load', (e) => {
const pattern = ctx.createPattern(image, "repeat");
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, 200, 150);
});
效果如下:

三、像素级图像应用
1、getImageData 方法
使用 CanvasRenderingContext2D 提供的 getImageData 来获取获取 Canvas 画布的设定区域的 ImageData 对象,从而拿到图片像素数据。
ctx.getImageData(x, y, width, height);
参数:
x:将要被提取的图像数据矩形区域的左上角 x 坐标。y:将要被提取的图像数据矩形区域的左上角 y 坐标。width:将要被提取的图像数据矩形区域的宽度。height:将要被提取的图像数据矩形区域的高度。
返回值:一个 ImageData 对象,用来描述 canvas 区域隐含的像素数据。
1-1、基本用法
// HTML
<canvas id="canvas" width="200" height="150" style="border: 1px dashed gray;"></canvas>
<img id="image" src="./image.png" alt="image" />
// JS
const cvs = document.getElementById('canvas')
const ctx = cvs.getContext('2d')
const image = document.getElementById("image");
image.addEventListener('load', (e) => {
ctx.drawImage(image, 50, 0);
console.log(ctx.getImageData(0, 0, 200, 150))
});
运行控制台可能会报错:

这是由于 getImageData() 需要以服务器形式启动才能使用(跨域限制),以文件形式打开会报错,点击查看解决 canvas 图片 getImageData、toDataURL 跨域问题
如果是用 VSCode,可以安装 Live Server 插件,然后右键运行 Open with Live Server 即可。
运行正常后控制台输出:

其中,
width、height:表示所选区域的宽高;data:保存了区域的像素数据数组,数组取值如[r1, g1, b1, a1, r2, g2, b2, a2],每四个数存储着 1 个像素的rgba颜色值,data.length表示图片像素总量。
1-2、实现简易取色器
// HTML
<canvas id="canvas" width="200" height="150" style="border: 1px dashed gray;"></canvas>
<img id="image" src="./image.png" alt="image" />
// JS
const cvs = document.getElementById('canvas')
const ctx = cvs.getContext('2d')
const image = document.getElementById("image");
const getPixelColor = (e) => {
const pixel = ctx.getImageData(e.offsetX, e.offsetY, 1, 1).data;
const rgba = `rgba(${pixel[0]}, ${pixel[1]}, ${pixel[2]}, ${pixel[3] / 255})`;
console.log(rgba);
}
image.addEventListener('load', (e) => {
ctx.drawImage(image, 50, 0);
cvs.addEventListener("mousemove", getPixelColor);
});
鼠标悬浮画布时控制台会打印指针指向位置的颜色值。
2、putImageData 方法
在获取到图片的像素数据后,就可以对获取的像素数据进行处理,比如进行灰度化或反色处理。完成处理后,可以使用 CanvasRenderingContext2D 提供的另一个 API —— putImageData 在页面上显示处理效果。
ctx.putImageData(imagedata, dx, dy);
ctx.putImageData(imagedata, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight);
参数:
imageData:包含图片像素信息的 ImageData 数组对象,用于替换当前 Canvas 画布上的 ImageData。dx:目标 Canvas 中被图像数据替换的起点横坐标。dy:目标 Canvas 中被图像数据替换的起点纵坐标。dirtyX(可选):图像数据渲染区域的左上角横坐标,默认值为 0。dirtyY(可选):图像数据渲染区域的左上角纵坐标,默认值为 0。dirtyWidth(可选):图像数据渲染区域的宽度,默认为 imagedata 图像的宽度。dirtyHeight(可选):图像数据渲染区域的高度,默认为 imagedata 图像的高度。
2-1、实现图片灰度化
点击查看利用 getImageData 和 putImageData 实现图片灰度化
四、图像保存
可通过 toDataURL 实现图像压缩及保存,点击查看详情
五、Canvas 图像优化
- 避免浮点数的坐标点,用整数取而代之:
// ❌
ctx.drawImage(myImage, 0.3, 0.5);
// ✅
ctx.drawIamge(myImage, Math.floor(0.3), Math.floor(0.5));
- 不要在用
drawImage时缩放图像,可以缓存图片的不同尺寸; - 使用多层画布去画一个复杂的场景,比如静态背景和频繁发生移动的对象;
- 用 CSS 设置大的背景图,避免在每一帧在画布上反复地绘制大图;
- 用 CSS transform 特性缩放画布以开启硬件加速。当然最好是不直接缩放画布,或者让小画布按比例放大,而非大画布按比例缩小;
- 关闭透明度:
const ctx = canvas.getContext("2d", { alpha: false });
- 将画布的函数调用集合到一起(例如,画一条折线,而不要画多条分开的直线)
- 避免不必要的画布状态改变;
- 渲染画布中的不同点,而非整个新状态;
- 尽可能避免
shadowBlur特性; - 尽可能避免
text rendering; - 不同的场景可以选择不同的清除画布的方法:
clearRect()、fillRect()、调整 canvas 大小; - 使用 window.requestAnimationFrame() 而非
window.setInterval(); - 谨慎使用大型物理库。