应用场景
| 应用场景 | 典型用途示例 | 技术特征与优势 | 替代/对比技术 |
|---|---|---|---|
| 🎮 游戏开发 | 小游戏、2D 引擎、动画角色 | 高性能像素级绘制、支持复杂物理与碰撞检测 | WebGL、PixiJS |
| 📊 数据可视化 | 图表、仪表盘、实时数据图 | 支持高频率刷新、大数据量图表比 SVG 性能更优 | SVG、D3.js |
| 🖌 图形编辑器/白板 | 涂鸦、签名、协同绘图 | 支持路径绘制、事件响应、撤销重做、状态同步 | SVG、Fabric.js |
| 📹 视频处理/弹幕 | 视频滤镜、美颜、弹幕、水印 | 可从 <video> 读取帧数据并像素处理,结合 captureStream 合成输出 | WebGL、FFmpeg WASM |
| 🧩 图像处理/导出 | 压缩、裁剪、滤镜、分享图生成 | 像素级处理,toDataURL() 导出图片 | OffscreenCanvas |
| 🌠 UI 动效/视觉装饰 | 背景特效、粒子轨迹、光影模拟 | 结合 requestAnimationFrame() 实现高帧率视觉表现 | CSS 动画、SVG |
| 🎯 命中测试/物理模拟 | 像素选区、热点检测、弹跳模拟 | getImageData() 精确像素检测 | 无直接替代方案 |
基本框架--最小实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Canvas 入门</title>
</head>
<body>
<canvas id="canvas"> 该浏览器不支持Canvas,请升级浏览器 </canvas>
</body>
<script>
/** @type {HTMLCanvasElement} */
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
// 绘制直线
ctx.moveTo(10, 10);
ctx.lineTo(50, 50);
ctx.stroke();
</script>
</html>
注意点
- canvas 默认宽300,高150
- canvas 标签中内容为浏览器不支持canvas标签的时候显示
- 设置 canvas 属性 width、height 的同时使用 Css 设置 canvas 宽高会出现拉伸、模糊的情况,其实就是进行了缩放,所以尽量使用canvas 自带属性来设置宽高。如图:
<canvas id="canvas" style="width: 300px; height: 300px">会变成下面情况,默认属性值和css值不一样导致拉伸
绘制形状
直线
API讲解
.moveTo(x,y): 将笔触移动到指定的坐标 x 以及 y 上.lineTo(x,y): 绘制一条从当前位置到指定 x 以及 y 位置的直线.stroke(): 通过线条来绘制图形轮廓
示例:
ctx.moveTo(10, 10);
ctx.lineTo(50, 50);
ctx.stroke();
三角形
绘制三角形就是在三条直线连接起来
API讲解
.fill(): 通过填充路径的内容区域生成实心的图形
示例:
ctx.moveTo(10, 10);
ctx.lineTo(50, 50);
ctx.lineTo(10, 50);
ctx.lineTo(10, 10);
// 空心三角
ctx.stroke();
// 实心三角
ctx.fill();
矩形
矩形一样可以由4条直线连接,但是 Canvas 有直接绘制矩形的API
API讲解
.strokeRect(x,y,width,height): 绘制矩形边框.fillRect(x,y,width,height): 绘制填充的矩形.clearRect(x,y,width,height): 清除指定矩形区域,让清除部分完全透明.rect(x,y,width,height): 创建一个矩形路径,然后选择使用 fill 或 stroke 填充或描边
示例:
ctx.strokeRect(210, 210, 50, 50);
ctx.fillRect(100, 100, 100, 100);
ctx.clearRect(110, 110, 40, 40);
弧、圆
API讲解:
.beginPath(): 新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径.closePath(): 闭合路径,使图形绘制命令又重新指向到上下文中.arc(x, y, radius, startAngle, endAngle, anticlockwise): 画一个以(x,y)为圆心的以 radius 为半径的圆弧(圆),从 startAngle 开始到 endAngle 结束,按照 anticlockwise 给定的方向(默认为顺时针)来生成。.arcTo(x1, y1, x2, y2, radius): 通过给定2个控制点(x1,y1) (x2,y2)和半径radius向当前子路径添加一个圆弧。如果需要,例如起始点和控制点在一条直线上,该圆弧会自动与路径的最后一个点用直线连接。
ctx.arc(200, 200, 50, 0, Math.PI); // 顺时针
ctx.stroke();
ctx.arc(300, 300, 50, 0, Math.PI / 3, false); // 逆时针
ctx.stroke();
出现了连笔的情况,这是因为在绘制圆和圆弧的时候,上下文的用了一笔来绘制,当绘制了圆之后连续绘制到点(350,300),然后开始绘制圆弧
解决1: 使用 .beginPath()、.closePath() 新建路径
ctx.arc(200, 200, 50, 0, Math.PI * 2);
ctx.stroke();
ctx.beginPath();
ctx.arc(300, 300, 50, 0, Math.PI / 3, false);
ctx.stroke();
ctx.closePath();
解决2: 使用 .moveTo() 移动到新的起始点开始绘制,也是上面效果
ctx.arc(200, 200, 50, 0, Math.PI * 2);
ctx.stroke();
ctx.moveTo(350, 300);
ctx.arc(300, 300, 50, 0, Math.PI / 3, false);
ctx.stroke();
使用 .arcTo
// 切线段
ctx.beginPath();
ctx.strokeStyle = "gray";
ctx.moveTo(200, 20);
ctx.lineTo(200, 130);
ctx.lineTo(50, 20);
ctx.stroke();
// 圆弧
ctx.beginPath();
ctx.strokeStyle = "black";
ctx.lineWidth = 5;
ctx.moveTo(200, 20);
ctx.arcTo(200, 130, 50, 20, 40);
ctx.stroke();
// 起始点
ctx.beginPath();
ctx.fillStyle = "blue";
ctx.arc(200, 20, 5, 0, 2 * Math.PI);
ctx.fill();
// 控制点
ctx.beginPath();
ctx.fillStyle = "red";
ctx.arc(200, 130, 5, 0, 2 * Math.PI); // 控制点一
ctx.arc(50, 20, 5, 0, 2 * Math.PI); // 控制点二
ctx.fill();
椭圆
API讲解:
-
.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise):x、y:椭圆的圆心位置
radiusX、radiusY:x轴和y轴的半径
rotation:椭圆的旋转角度,以弧度表示
startAngle:开始绘制点
endAngle:结束绘制点
anticlockwise:绘制的方向(默认顺时针),可选参数。
示例:
// 圆心(100,100) x轴半径50 y轴半径75 默认顺时针旋转45°(Math.PI / 4) 从0°开始绘制360°
ctx.ellipse(100, 100, 50, 75, Math.PI / 4, 0, 2 * Math.PI);
ctx.stroke();
// 对称轴
ctx.moveTo(0, 200);
ctx.lineTo(200, 0);
ctx.stroke();
二次贝塞尔曲线
API讲解:
.quadraticCurveTo(cpx, cpy, x, y): cpx和cpy为控制点坐标,x和y为结束点坐标
新增二次贝塞尔曲线路径需要 3 个点。分别是起始点、控制点、终点。起始点是当前路径最新的点,在创建二次贝赛尔曲线之前,可以使用 moveTo() 方法进行改变起始点坐标
示例:
// 二次贝塞尔曲线
ctx.moveTo(50, 20);
ctx.quadraticCurveTo(230, 30, 50, 100);
ctx.stroke();
// 控制点
ctx.beginPath();
ctx.arc(230, 30, 5, 0, 2 * Math.PI);
ctx.fill();
// 起始点和结束点
ctx.beginPath();
ctx.arc(50, 20, 5, 0, 2 * Math.PI); // 起始点
ctx.arc(50, 100, 5, 0, 2 * Math.PI); // 结束点
ctx.fill();
推荐在线工具: 二次贝塞尔曲线
三次贝塞尔曲线
API讲解:
.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y): cp1x和cp1y为第一个控制点坐标,cp2x和cp2y为第二个控制点坐标,x和y为结束点坐标
示例:
// 点的坐标
let start = { x: 50, y: 20 }; // 起始点
let cp1 = { x: 230, y: 30 }; // 控制点1
let cp2 = { x: 150, y: 80 }; // 控制点2
let end = { x: 250, y: 100 }; // 终点
// 三次贝塞尔曲线
ctx.beginPath();
ctx.moveTo(start.x, start.y);
ctx.bezierCurveTo(cp1.x, cp1.y, cp2.x, cp2.y, end.x, end.y);
ctx.stroke();
// 起点和终点
ctx.beginPath();
ctx.arc(start.x, start.y, 5, 0, 2 * Math.PI); // 起点
ctx.arc(end.x, end.y, 5, 0, 2 * Math.PI); // 终点
ctx.fill();
// 控制点
ctx.beginPath();
ctx.arc(cp1.x, cp1.y, 5, 0, 2 * Math.PI); // 控制点一
ctx.arc(cp2.x, cp2.y, 5, 0, 2 * Math.PI); // 控制点二
ctx.fill();
推荐在线工具:三次贝塞尔曲线
Path2D
允许 canvas 中根据需要创建可以保留并重用的路径
Path2D 对象方法:
- 与
CanvasRenderingContext2D接口对象中的相同方法:
.closePath() .moveTo() .lineTo() .bezierCurveTo() .quadraticCurveTo() .arc() .arcTo() .ellipse() .rect()
- 实例方法:
.addPath(path, transform): 添加一条新路径到对当前路径。path: 添加的 Path2D 路径。transform(可选): DOMMatrix 作为新增路径的变换矩阵。
// 创建 Path2D 路径
const p1 = new Path2D();
p1.rect(0, 0, 100, 150);
const p2 = new Path2D();
// 填充 p1 路径
ctx.fill(p1);
// p1 加入到 p2,并平移
ctx.fillStyle = "red";
p2.addPath(p1);
ctx.translate(120, 0);
ctx.fill(p2);
绘制样式
strokeStyle--描边颜色等、lineWidth--线条宽度
-
strokeStyle: 指定用于形状描边(轮廓)的颜色、渐变或图案。默认值是 #000(黑色)。color: CSS <color> 值的字符串 gradient: CanvasGradient 对象(线性或径向渐变) pattern: CanvasPattern 对象(重复图像) -
lineWidth: 指定线条的宽度(以坐标空间单位表示)。值为正数,零、负数、Infinity 和 NaN 值将被忽略。默认值为 1.0
演示
ctx.strokeStyle = "blue"; // 设置颜色
ctx.lineWidth = 5; // 设置线条宽度
ctx.moveTo(20, 20);
ctx.lineTo(130, 130);
ctx.stroke();
lineCap--绘制线段的末端
lineCap: 指定如何绘制线段的末端。值为
butt: 线条末端呈正方形。这是默认值
round: 线条末端呈圆形的
square: 线条末端呈方形,通过添加一个宽度与线条粗细相同且高度粗细的一半的盒子来形成。
演示
// 绘制辅助线
ctx.strokeStyle = "#09f";
ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(140, 10);
ctx.moveTo(10, 140);
ctx.lineTo(140, 140);
ctx.stroke();
// 绘制线条
ctx.strokeStyle = "black";
["butt", "round", "square"].forEach((lineCap, i) => {
ctx.lineWidth = 15;
ctx.lineCap = lineCap;
ctx.beginPath();
ctx.moveTo(25 + i * 50, 10);
ctx.lineTo(25 + i * 50, 140);
ctx.stroke();
});
lineJoin--线段如何连接
lineJoin: 用于设置 2 个线段如何连接在一起,这个属性在两个连接的线段具有相同方向(如两线段组成一个线段)时没有效果,因为在这种情况下不会添加连接区域。长度为零的退化线段(即所有端点和控制点处于完全相同的位置)也会被忽略。默认值:miter
round: 通过填充一个额外的,圆心在相连部分末端的扇形,绘制拐角的形状。圆角的半径是线段的宽度
bevel: 在相连部分的末端填充一个额外的以三角形为底的区域,每个部分都有各自独立的矩形拐角
miter: 通过延伸相连部分的外边缘,使其相交于一点,形成一个额外的菱形区域。这个设置受到 miterLimit 属性的影响。
演示
ctx.lineWidth = 10;
["round", "bevel", "miter"].forEach((join, i) => {
ctx.lineJoin = join;
ctx.beginPath();
ctx.moveTo(-5, 5 + i * 40);
ctx.lineTo(35, 45 + i * 40);
ctx.lineTo(75, 5 + i * 40);
ctx.lineTo(115, 45 + i * 40);
ctx.lineTo(155, 5 + i * 40);
ctx.stroke();
});
miterLimit--设置斜接限制比例
miterLimit: 用于设置斜接限制比例,于坐标空间单位中。零、负数、Infinity 和 NaN 值将被忽略。默认值为 10.0,所谓斜接长度指线条交接处内角顶点到外角顶点的长度,线段之间夹角比较大时,交点不会太远,但随着夹角变小,交点距离会呈指数级增大;如果交点距离大于miterLimit值,连接效果会变成了 lineJoin = bevel 的效果
演示
ctx.lineWidth = 7;
ctx.miterLimit = 8;
// 绘制线条
ctx.beginPath();
ctx.moveTo(0, 100);
for (let i = 0; i < 24; i++) {
// 每段折线在 Y 方向上的偏移:偶数索引向下偏移 25,奇数索引向上偏移 25,形成波浪形折线
const dy = i % 2 === 0 ? 25 : -25;
// 计算当前点的 X 坐标,使用幂函数 Math.pow(i, 1.5) * 2 使 X 间距逐渐变大
ctx.lineTo(Math.pow(i, 1.5) * 2, 75 + dy);
}
ctx.stroke();
setLineDash--虚线样式
-
.setLineDash(segments): 用于在描线时使用虚线模式。使用正数数据来指定描述模式的线和间隙的交替长度。如果数组元素的数量是奇数,数组的元素会被复制和拼接。例如,[5, 15, 25]会变成[5, 15, 25, 5, 15, 25]。如果数组为空,则虚线列表会被清空,线条描边将恢复为实线 -
.getLineDash(): 返回当前虚线设置样式的非负偶数的数组 -
lineDashOffset: 设置虚线样式的起始偏移量,值为一个浮点数,默认值为0.0
演示
let y = 15;
function drawDashedLine(pattern) {
ctx.beginPath();
ctx.setLineDash(pattern);
ctx.moveTo(0, y);
ctx.lineTo(300, y);
ctx.stroke();
y += 20;
console.log(ctx.getLineDash()); // [15, 3, 8, 3] [12, 3, 3, 12, 3, 3]
}
drawDashedLine([15, 3, 8, 3]);
drawDashedLine([12, 3, 3]);
ctx.setLineDash([4, 16]);
// 无偏移量的虚线
ctx.beginPath();
ctx.moveTo(0, 80);
ctx.lineTo(300, 80);
ctx.stroke();
// 偏移量为 4 的虚线
ctx.beginPath();
ctx.strokeStyle = "red";
ctx.lineDashOffset = 4;
ctx.moveTo(0, 100);
ctx.lineTo(300, 100);
ctx.stroke();
globalAlpha--透明度
globalAlpha: 值是一个在 0.0(完全透明)到 1.0(完全不透明)之间的数字,包括两者在内。默认值是 1.0。超出该范围的值,包括 Infinity 和 NaN,将不会被设置,并且 globalAlpha 将保留其先前的值。
演示
// 不透明
ctx.fillStyle = "red";
ctx.fillRect(160, 50, 100, 100);
// 透明
ctx.globalAlpha = 0.5;
ctx.fillStyle = "red";
ctx.fillRect(50, 50, 100, 100);
渐变
-
线性渐变
.createLinearGradient(x0, y0, x1, y1): 指定四个参数,分别表示渐变线段的起点(x0,y0)和终点(x1,y1),返回一个线段初始化的线性 CanvasGradient -
径向渐变:
.createRadialGradient(x0, y0, r0, x1, y1, r1): 指定六个参数,(x0,y0,r0)定义渐变的起始圆,(x1,y1,r1)定义渐变的结束圆,返回一个使用指定两个圆初始化的径向 CanvasGradient -
CanvasGradient.addColorStop(offset, color):offset: 一个在 0 到 1 之间(包含边界)的数字,表示色标的位置。0 表示渐变的起始位置,1 表示渐变的结束位置。 color: 色标的颜色
演示:
// 创建线性渐变
// 渐变起点(20,0) 渐变终点(220,0)
const lineGradient = ctx.createLinearGradient(20, 0, 220, 0); // 返回 CanvasGradient
// 使用 CanvasGradient.addColorStop 添加三个色标
lineGradient.addColorStop(0, "green");
lineGradient.addColorStop(0.5, "cyan");
lineGradient.addColorStop(1, "green");
ctx.fillStyle = lineGradient;
ctx.fillRect(20, 220, 200, 100);
// 创建径向渐变
// 起始圆(110,90,30) 结束圆(100,100,70)
const radialGradient = ctx.createRadialGradient(110, 90, 30, 100, 100, 70);
radialGradient.addColorStop(0, "pink");
radialGradient.addColorStop(0.4, "white");
radialGradient.addColorStop(1, "green");
ctx.fillStyle = radialGradient;
ctx.fillRect(20, 20, 160, 160);
createPattern--图案样式
.createPattern(image, repetition): 用于使用指定的图像或重复创建图案。此方法返回一个 CanvasPattern 对象。这个方法并不直接在画布上绘制任何内容。它所创建的图案必须赋值给fillStyle或strokeStyle,之后才会应用于接下来的绘制操作。
image 参数值:
HTMLImageElement(<img>)-- .png 等图片
SVGImageElement(<image>)-- svg 图
HTMLVideoElement(<video>,通过使用视频捕获)-- 视频
HTMLCanvasElement(<canvas>)-- canvas 图案
ImageBitmap -- 位图图像
OffscreenCanvas -- 脱离主线程的 Canvas 离屏渲染容器
VideoFrame -- 视频帧的高效封装
repetition 参数值:
repeat、null、'' -- 两个方向都重复
repeat-x -- 仅水平方向重复
repeat-y -- 仅垂直方向重复
no-repeat -- 两个方向都不重复
演示:
const img = new Image();
img.src = "./img.jpg";
// 图像加载完成后再使用
img.onload = () => {
const pattern = ctx.createPattern(img, "no-repeat");
// const pattern = ctx.createPattern(img, "repeat");
// const pattern = ctx.createPattern(img, "repeat-x");
// const pattern = ctx.createPattern(img, "repeat-y");
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, 600, 600);
};
绘制文本
strokeText、fillText
-
.strokeText(text, x, y, maxWidth)和.fillText(text, x, y, maxWidth):text: 文本字符串。文本根据 font、textAlign、textBaseline 和 direction 指定的设置进行渲染。 (x,y): 绘制文本起始点坐标 maxWidth(可选): 渲染后文本的最大宽度,如果提供了该值,用户代理将调整字距,选择水平方向更紧凑的字体或缩小字体大小,以便在指定宽度内容纳文本。 -
font: 指定绘制文字所使用的当前字体样式,默认字体为 10 像素的无衬线体(sans-serif)
演示:
ctx.font = "bold 48px serif";
ctx.strokeText("strokeText", 50, 90);
ctx.fillText("fillText", 50, 190);
// 设置 maxWidth
ctx.fillText("fillText", 50, 290, 100);
文本样式
font: 前面提过textAlign: 文本对齐的方式。可选值为:left、right、center、start()和end。默认值是 start。direction: 文本的方向。可选值为:ltr(文本方向从左向右)、rtl(文本方向从右向左)、inherit(文字方向从相应的 元素或 Document 继承)。默认值是 inherit。
注意:
- 当 direction = 'rtl'的时候,如果字符串以特殊符号结尾,那么特殊符号会显示在 end,也就是左边,例如: Hi! 会显示为 !Hi
- direction 属性会对 textAlign 属性产生影响。如果 direction 属性设置为 ltr,则textAlign属性的 left 和 start 的效果相同,right 和 end 的效果相同,如果 direction 属性设置为 rtl,则 textAlign属性的 left 和 end 的效果相同,right 和 start 的效果相同
direction演示:
ctx.font = "48px serif";
ctx.fillText("Hi!", 100, 200); // 默认 inherit ,这里就是 ltl
ctx.direction = "rtl";
ctx.fillText("Hi!", 100, 300);
textAlign演示:
ctx.moveTo(300, 0);
ctx.lineTo(300, 500);
ctx.stroke();
// 一般文本对齐,值为 left center right
ctx.font = "30px serif";
ctx.textAlign = "left"; // 左对齐
ctx.fillText("left-aligned", 300, 40);
ctx.textAlign = "center"; // 居中
ctx.fillText("center-aligned", 300, 85);
ctx.textAlign = "right"; // 右对齐
ctx.fillText("right-aligned", 300, 130);
// 依赖方向的文本对齐
ctx.direction = "ltr";
ctx.textAlign = "start"; // 相当于 left
ctx.fillText("ltr-Start-aligned", 300, 240);
ctx.textAlign = "end"; // 相当于 right
ctx.fillText("ltr-End-aligned", 300, 240);
ctx.direction = "rtl";
ctx.textAlign = "start"; // 相当于 right
ctx.fillText("rtl-Start-aligned", 300, 380);
ctx.textAlign = "end";
ctx.fillText("rtl-Start-aligned", 300, 380);
-
textBaseline: 描述绘制文本时使用的文本基线,默认值:alphabetictop: 文本基线在文本块的顶部 hanging: 文本基线是悬挂基线(用于藏文和其他印度文字) middle: 文本基线在文本块的中间 alphabetic: 文本基线是标准的字母基线 ideographic: 文字基线是表意字基线;如果字符本身超出了 alphabetic 基线,那么 ideograhpic 基线位置在字符本身的底部(用于中文、日文和韩文) bottom: 文本基线在文本块的底部。与 ideographic 基线的区别在于 ideographic 基线不需要考虑下行字母
演示:
const baselines = [
"top",
"hanging",
"middle",
"alphabetic",
"ideographic",
"bottom",
];
ctx.font = "20px serif";
ctx.strokeStyle = "red";
ctx.moveTo(0, 100);
ctx.lineTo(840, 100);
ctx.moveTo(0, 55);
ctx.stroke();
baselines.forEach((baseline, index) => {
ctx.save(); // 用于将当前状态放入栈中,保存 canvas 的状态
ctx.textBaseline = baseline;
let x = index * 120 + 10;
ctx.fillText("Abcdefghijk", x, 100);
ctx.restore(); // 恢复到最近一次调用 save() 时保存的状态
ctx.fillText(baseline, x + 5, 50);
});
measureText -- 测量文本
.measureText(text): 该方法返回一个包含被测量文本相关信息(例如它的宽度)的 TextMetrics 对象,text为需要测量的文本字符串
注意:
演示:返回的 TextMetrics 对象的属性都是只读
const text = ctx.measureText("Hello world");
console.log(text.width); // 53.729736328125
阴影
-
shadowOffsetX和shadowOffsetY属性: 用来设定阴影在 X 和 Y 轴的延伸距离,不受变换矩阵所影响的。负值表示阴影会往上或左延伸,正值则表示会往下或右延伸,默认为 0。 -
shadowBlur属性: 用于设定阴影的模糊程度,其数值并不跟像素数量挂钩,也不受变换矩阵的影响,默认为 0 -
shadowColor属性: 用于设定阴影颜色效果,默认是全透明的黑色
// 设置阴影
ctx.shadowColor = "rgba(255, 0, 0, 0.8)";
ctx.shadowBlur = 8;
ctx.shadowOffsetX = 30;
ctx.shadowOffsetY = 20;
// 矩形
ctx.fillStyle = "rgba(0, 255, 0, 0.2)";
ctx.fillRect(10, 10, 150, 100);
// 矩形的边
ctx.lineWidth = 10;
ctx.strokeStyle = "rgba(0, 0, 255, 0.6)";
ctx.strokeRect(10, 10, 150, 100);
绘制图像 -- drawImage
和 createPattern() 可以用来绘制图像,但使用方式和场景有区别
| 功能项 | drawImage() | createPattern() |
|---|---|---|
| 目的 | 绘制图像 | 创建基于图像的重复图案 |
| 用法 | ctx.drawImage(image, x, y) | ctx.fillStyle = ctx.createPattern(img, repeat) |
| 作用对象 | 直接绘制图像到 canvas | 设置图案填充样式,需配合 fillRect() 使用 |
| 图像是否平铺 | ❌ 否,只绘制一次 | ✅ 是,可以设置为重复铺满 |
| 剪裁/缩放支持 | ✅ 支持(有多个重载) | ❌ 不支持剪裁,整图平铺 |
| 性能 | 更适合单次绘制 | 更适合大面积图案填充 |
| 示例用途 | 渲染一张图片、贴图、角色等 | 背景纹理、墙面砖块、地板图案等 |
.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight): 用于直接绘制一张图片到指定位置
注意: drawImage()方法根据传参个数实现不同功能,有三种传参方式
- drawImage(image, dx, dy)
- drawImage(image, dx, dy, dWidth, dHeight)
- drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
参数说明:
image: 和 createPattern() 的image 参数值一样
sx、sy:裁剪框的左上角(相对于源 image )X轴坐标和Y轴坐标,可以使用 3 参数或 5 参数语法来省略这个参数
sWidth、sHeight:裁剪框的宽度和高度
dx、dy:源 image 的左上角在目标画布上 X 轴坐标、 y 轴坐标
dWidth、dHeight:image 在目标画布上绘制的宽度。允许对绘制的图像进行缩放。如果不指定,在绘制时 image 宽度不会缩放
const image = new Image();
image.src = "./img.jpg";
image.onload = function () {
ctx.drawImage(image, 50, 50); // drawImage(image, dx, dy)
};
ctx.drawImage(image, 50, 50, 200, 100);
ctx.drawImage(image, 50, 50);
ctx.drawImage(image, 60, 60, 100, 100, 50, 550, 200, 100);
变换状态
save、restore -- 状态的保存和恢复
-
.save(): 用于通过将当前状态放入栈中,以保存 canvas 的完整状态保存到栈中的绘制状态有下面部分组成: 当前的变换矩阵 当前的剪切区域 当前的虚线列表 以下属性当前的值:strokeStyle、fillStyle、globalAlpha、lineWidth、 lineCap、lineJoin、miterLimit、lineDashOffset、shadowOffsetX、 shadowOffsetY、shadowBlur、shadowColor、globalCompositeOperation、 font、textAlign、textBaseline、direction、imageSmoothingEnabled。 -
.restore(): 用于通过在绘制状态栈中弹出顶部的条目,将 canvas 恢复到最近的保存状态。如果没有保存状态,此方法不做任何改变
// 保存 fillStyle
ctx.save();
ctx.fillStyle = "green";
ctx.fillRect(10, 10, 100, 100);
// 恢复到最近一次调用 save() 保存的状态
ctx.restore();
ctx.fillRect(150, 40, 100, 100);
移动、旋转和缩放
translate(x,y)
用于对当前网格添加平移变换,x 是左右偏移量,y 是上下偏移量
ctx.fillRect(150, 40, 100, 100); // 未平移
ctx.translate(120, 0);
ctx.fillRect(150, 40, 100, 100); // 平移
.rotate(angle)
用于在变换矩阵中增加旋转, angle 是顺时针旋转弧度。旋转中心是 canvas 的原点,如果想改变中心点,使用 translate() 移动画布
1. 以原点旋转
// canvas 原点
ctx.arc(0, 0, 10, 0, 2 * Math.PI);
ctx.fillStyle = "red";
ctx.fill();
// 未旋转的矩形
ctx.fillStyle = "gray";
ctx.fillRect(100, 0, 80, 20);
// 旋转的矩形
ctx.rotate((45 * Math.PI) / 180);
ctx.fillStyle = "red";
ctx.fillRect(100, 0, 80, 20);
2. 围绕中心旋转
// 未旋转的矩形
ctx.fillStyle = "gray";
ctx.fillRect(80, 60, 140, 30);
// 矩阵变换 (150,75)是矩形中心点
ctx.translate(150, 75); // 使用tranlate把旋转中心由 canvas 原点变矩形中心点
ctx.rotate(Math.PI / 2); // 旋转 90°
ctx.translate(-150, -75); // 使用tranlate把旋转中心改回去
// 旋转后的矩形
ctx.fillStyle = "red";
ctx.fillRect(80, 60, 140, 30);
.scale(x, y)
用于根据水平和垂直方向,为 canvas 单位添加缩放变换,x和y的值小于1则为缩小,大于1则为放大。默认值为 1
// 未缩放的矩形
ctx.fillStyle = "gray";
ctx.fillRect(10, 10, 8, 20);
// 缩放后的矩形
ctx.scale(9, 3);
ctx.fillStyle = "red";
ctx.fillRect(10, 10, 8, 20);
transform setTransform resetTransform
.transform(a, b, c, d, e, f): 用于将由该方法的参数所描述的矩阵与当前的变换相乘.setTransform(a, b, c, d, e, f): 该方法会将当前变形矩阵重置为单位矩阵,然后用相同的参数调用 transform.resetTransform(): 该方法可以重置当前变形为单位矩阵。等同于调用 setTransform(1, 0, 0, 1, 0, 0).getTransform(): 检索矩阵
如果一个点原始坐标为 (x,y),经过变换后,其坐标将变为 (ax+cy+e,bx+dy+f)。 参数说明:
a: 水平方向的缩放
b: 竖直方向的倾斜偏移
c: 水平方向的倾斜偏移
d: 竖直方向的缩放
e: 水平方向的移动
f: 竖直方向的移动
当 b 和 c 为 0 时,a 和 d 控制上下文的水平和垂直缩放
当 a 和 d 为 1 时,b 和 c 控制上下文的水平和垂直倾斜
// 进行垂直(.2)和水平(.8)方向的倾斜,同时保持缩放和平移不变
ctx.transform(1, 0.2, 0.8, 1, 0, 0);
ctx.fillRect(0, 0, 100, 100);
ctx.setTransform(1, 0.2, 0.8, 1, 0, 0);
ctx.fillRect(0, 120, 100, 100);
console.log(ctx.getTransform());// DOMMatrix: {a:1,b:0.2 ...}
ctx.resetTransform();
console.log(ctx.getTransform()); // DOMMatrix: {a:1,b:0 ...}
合成 -- globalCompositeOperation
案例推荐: MDN-globalCompositeOperation
设置要在绘制新形状时应用的合成操作的类型
属性值:
source-over: 默认值,在现有画布上绘制新图形source-in: 仅在新形状和目标画布重叠的地方绘制新形状。其他的都是透明的source-out: 在不与现有画布内容重叠的地方绘制新图形source-atop: 只在与现有画布内容重叠的地方绘制新图形
destination-over: 在现有画布内容的后面绘制新的图形destination-in: 仅保留现有画布内容和新形状重叠的部分。其他的都是透明的destination-out: 仅保留现有画布内容和新形状不重叠的部分destination-atop: 仅保留现有画布内容和新形状重叠的部分。新形状是在现有画布内容的后面绘制的
lighter: 两个重叠图形的颜色是通过颜色值相加来确定的copy: 只显示新图形xor: 形状在重叠处变为透明,在其他地方正常绘制multiply: 将顶层像素与底层相应像素相乘,结果是一幅更黑暗的图片screen: 像素被倒转、相乘、再倒转,结果是一幅更明亮的图片(与 multiply 相反)overlay: multiply 和 screen 的结合。原本暗的地方更暗,原本亮的地方更亮darken: 保留两个图层中最暗的像素lighten: 保留两个图层中最亮的像素
color-dodge: 将底层除以顶层的反置color-burn: 将反置的底层除以顶层,然后将结果反过来
hard-light: 类似于 overlay,multiply 和 screen 的结合——但上下图层互换了soft-light: 柔和版本的 hard-light。纯黑或纯白不会导致纯黑或纯白difference: 从顶层减去底层(或反之亦然),始终得到正值exclusion: 与 difference 类似,但对比度较低hue: 保留底层的亮度(luma)和色度(chroma),同时采用顶层的色调(hue)saturation: 保留底层的亮度和色调,同时采用顶层的色度color: 保留了底层的亮度,同时采用了顶层的色调和色度luminosity: 保持底层的色调和色度,同时采用顶层的亮度
裁剪 -- clip
用于将当前或给定路径转换为当前裁剪区域。前面的裁剪区域(如果有的话)将与当前路径或给定路径相交,从而创建新的裁剪区域
和 source-in source-atop 的区别
| 功能项 | clip() | source-in / source-atop |
|---|---|---|
| 控制方式 | 空间裁剪(路径) | 像素混合(像素叠加关系) |
| 绘制区域 | 仅限裁剪路径内部 | 基于已有像素的重叠部分 |
| 是否可嵌套叠加 | ✅ 可连续调用形成复合裁剪路径 | ❌ 不支持组合,后续操作会替代前一个混合方式 |
| 典型用途 | 动态遮罩、圆形头像、局部区域绘图限制 | 图像蒙版、图层合成、遮罩转场动画 |
| 性能表现 | 较高(路径裁剪硬件加速) | 中等(像素级运算,复杂度较高) |
| 图像平铺是否受影响 | ✅ 可控制路径内平铺 | ❌ 图像实际绘制但只在像素合成中显示处理 |
语法
.clip()使用上下文中当前路径裁剪.clip(path)path: 需要用作裁剪区域的 Path2D 路径.clip(fillRule)fillRule(暂未理解): 一个判断是在路径内还是在路径外的算法,允许的值有 nonzero(默认值):非零环绕原则,evenodd:奇偶环绕原则.clip(path, fillRule)
// 裁剪一个圆
ctx.arc(100, 75, 50, 0, Math.PI * 2);
ctx.clip();
ctx.fillRect(0, 0, canvas.width, canvas.height);
动画 -- window.requestAnimationFrame(callback)
canvas 实现动画可以依靠 window 的三个方法
-
window.setInterval(function, delay) -
window.setTimeout(function, delay) -
window.requestAnimationFrame(callback)(推荐) 告诉浏览器你希望执行一个动画。它要求浏览器在下一次重绘之前,调用用户提供的回调函数,若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用requestAnimationFrame(),因为requestAnimationFrame()是一次性的。
绘制动画的基本步骤
- 获取绘图上下文
- 定义动画开始状态
- 清空画布(每帧都要做),避免残影
- 保存 canvas 状态,避免每帧都是以原始状态绘制
- 绘制当前帧的内容
- 更新状态(位置、角度等)
- 恢复 canvas 状态
- 使用
requestAnimationFrame()循环绘制下一帧
// 1. 获取绘图上下文
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
// 2. 定义动画开始状态
const ball = {
x: 25,
y: 25,
radius: 25,
speed: 3,
};
function startAnimation() {
// 3. 清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 4. 保存 canvas 状态,避免每帧都是以原始状态绘制
ctx.save();
// 5. 绘制当前帧的内容
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.fill();
// 6. 更新状态(位置、角度等)
if (ball.x > canvas.width + ball.radius) ball.x = 0;
ball.x += ball.speed;
// 7. 恢复 canvas 状态
ctx.restore();
// 8. 使用 requestAnimationFrame() 循环绘制下一帧
requestAnimationFrame(startAnimation);
}
startAnimation();