前言
在上一篇内容,我们简单的讲解了一下Canvas 2D的代码使用与应用场景,这篇内容我将根据B站视频教程以及查阅MDN相关文档记录一下博主Canvas 2D的学习笔记。
一、基础
1、创建画布
html
<!-- 创建标签:一般在标签内写上width和height属性。如果同时在css定义W和H,因为css和画布属性大小比例不一致会引起内容的拉升或压缩。 -->
<canvas id="canvas" width="500" height="500">
<!-- 防止浏览器不支持canvas,所以在标签内可以添加替换元素。-->
<div>当前浏览器不支持canvas,请升级浏览器</div>
</canvas>
2、开画
javascript
// 1、获取元素
const canvas = doucument.getElementById('canvas')
// 2、获取上下文
const ctx = canvas.getContext('2d')
// 3、可以增加一个判断:处理没有获取到canvas
if(!!ctx === false){
// 不支持的兼容处理
return
}
二、如何画
1、坐标系

如上图所示:左上角为原点,水平往右为 X 轴的正坐标延伸,垂直向下为 Y 轴的正坐标延伸;
2、路径
可以绘制一笔或多笔。
1、绘制直线/折线/多边形
相关API:
.beginPath():用于通过清空子路径列表开始一个新路径。当你想创建一个新的路径时,调用此方法;.closePath():用于从当前点添加一条直线到当前子路径的起点。如果形状已经闭合或只有一个点,此函数将不执行任何操作;该方法并不直接在画布上绘制任何内容。你可以使用 stroke() 或 fill() 方法来渲染路径;.moveTo(x, y):用于在给定的 (x,y) 坐标处开始一个新的子路径。.lineTo(x, y):将当前子路径的最后一个点与指定的 (x, y) 坐标用直线段相连,从而将一个直线段添加到当前子路径中;和其他修改当前路径的方法一样,这个方法并不直接渲染任何内容。要将路径绘制到画布上,你可以使用 fill() 或 stroke() 方法;.stroke():用于根据当前的描边样式,绘制当前或指定的路径;描边与路径的中心对齐,也就是说,描边的一半位于路径的内侧,另一半位于外侧;描边使用非零环绕规则进行绘制,这意味着路径交叉点仍会被填充;.fill():用于根据当前的 fillStyle,填充当前或给定的路径。
javascript
// 1、新建画笔
ctx.beginPath()
/** 2、起点定位
* .moveTo(x, y)
*/
ctx.moveTo(50, 5)
/** 3、终点定位
* .lineTo(x, y)
*/
ctx.lineTo(10, 200) // 到这步其实画布上的直线没有显示出来
// 如果是要画折线的话,直接再来写一个lineTo,lineTo的终点坐标为起点的话,则成了一个多边形。
ctx.lineTo(160, 160)
// 4、显示轮廓:不然直线无法显示出来
ctx.stroke()
// 5、.fill()可以填充所画的内容,即使图形没有闭合
ctx.fill()
效果如下:

2、绘制长方形
相关API:
.rect(x, y, width, height):将一个矩形添加到当前路径中。与其他修改当前路径的方法一样,这个方法不会直接渲染任何内容。要在画布上绘制矩形,你可以使用 fill() 或 stroke() 方法;.strokeRect(x, y, width, height):根据当前的 strokeStyle 和其它设置描绘一个矩形的描边(轮廓)。此方法直接绘制到画布而不修改当前路径,因此任何后续 fill() 或 stroke() 调用对它没有影响;.fillRect(x, y, width, height):用于绘制一个矩形,并根据当前的 fillStyle 进行填充。这个方法是直接在画布上绘制填充,并不修改当前路径,所以在这个方法后面调用 fill() 或者 stroke() 方法并不会对这个方法有什么影响;.clearRect():用于通过把像素设置为透明黑色以达到擦除一个矩形区域的目的;如果没有正确使用路径,clearRect() 可能会导致意想之外的结果。请确保在调用 clearRect() 之后开始绘制新内容前调用 beginPath() 。
javascript
// 画一个长方形
ctx.rect(0, 0, 100, 100)
// 在不影响路径的情况下画一个长方形
ctx.strokeRect(0, 0, 100, 200)
// 有填充的长方形
ctx.fillRect(0, 0, 100, 200)
// 类似于橡皮擦的效果
ctx.clearRect(50, 50, 10, 100)

3、绘制圆形/贝塞尔曲线
相关API:
.arc(x, y, r, startAngle, endAngle, counterclockwise):用于将一个圆弧添加到当前子路径中。创建一个以坐标 (x, y) 为中心,以 radius 为半径的圆弧。路径从 startAngle 开始,到 endAngle 结束,路径方向由 counterclockwise 参数决定(默认为false/顺时针方向);.quadraticCurveTo(cpx, cpy, x, y):用于新增二次贝塞尔曲线路径。它需要 2 个点。第一个点是控制点,第二个点是终点。起始点是当前路径最新的点------在创建二次贝赛尔曲线之前,可以使用 moveTo() 方法进行改变。
圆形的角度是从x正轴开始为起点:

在绘制之前需要了解一下弧度制的公式:弧度=兀/180*角度
javascript
// 绘制一个半圆
/**
* 第一个参数是圆心 X 轴的坐标
* 第二个参数是圆心 Y 轴的坐标
* 第三个参数是半径
* 第四个参数是起始的弧度
* 第五个参数是结束的弧度。因为半圆的弧度为Math.PI/180*180=Math.PI
*/
ctx.arc(100, 100, 50, 0, Math.PI)
// 用stroke描绘路径或用fill填充图形
ctx.stroke()
效果如下:

贝塞尔曲线:是一种以伯恩斯坦基函数为加权系数对控制顶点进行线性组合所构造的参数曲线,广泛应用于计算机图形学等领域。
javascript
// 贝塞尔曲线示例
/**
* 有线性(二次)贝塞尔曲线
* 第一个参数:控制点的 X 轴坐标
* 第二个参数:控制点的 Y 轴坐标
* 第三个参数:终点的 X 轴坐标
* 第四个参数:终点的 Y 轴坐标
*/
ctx.quadraticCurveTo(230, 30, 50, 100)
效果如下:

3、文本
相关API:
.fillText(text, x, y, [, maxWidth]): 用于在指定的坐标上绘制文本字符串,并使用当前的 fillStyle 对其进行填充。存在一个可选参数,其指定了渲染文本的最大宽度,用户代理将通过压缩文本或使用较小的字体大小来实现。此方法会直接绘制到画布上,而不会修改当前路径,因此任何后续的 fill() 或 stroke() 调用都不会对其产生影响。文本根据 font、textAlign、textBaseline 和 direction 属性所定义的字体和文本布局来渲染。.strokeText(text, x, y, [, maxWidth]):用于在指定的坐标处对文本字符串的字符进行描边(即绘制轮廓)。一个可选的参数允许指定渲染文本的最大宽度,用户代理可以通过压缩文本或使用较小的字体大小来实现这一目标。这个方法直接绘制到画布上,而不修改当前路径,因此任何后续的 fill() 或 stroke() 调用对它没有影响。.font:指定绘制文字所使用的当前字体样式。使用和 CSS 字体描述符相同的字符串值。一个被解析为 CSS font 值的字符串。默认字体为 10 像素的无衬线体(sans-serif)。.textAlign:用于描述绘制文本时文本的对齐方式。对齐是相对于 fillText() 方法的 x 值的。例如,如果 textAlign 是 "center",那么该文本的左侧边界会是 x - (textWidth / 2)。.textBaseline:用于描述绘制文本时使用的文本基线。.direction:用来在绘制文本时,描述当前文本方向。
1、文本绘制
javascript
/**
* 第一个参数是展示的文本;
* 第二个参数是文本绘制的x坐标;
* 第三个参数收文本绘制的y坐标,注意这个 Y 轴的坐标起点是在文本的左下角为起点;
* 第四个参数是文本的最大宽度,非必须;文字超过最大宽度会被压缩。
*/
ctx.fillText('fillText:给岁月以文明,而不是给文明以岁月', 0, 10, 400)
ctx.strokeText('strokeText:给岁月以文明,而不是给文明以岁月', 50, 150, 100)
2、样式属性:font
javascript
/**
* 这几个样式属性应该在绘制之前定义好
*/
ctx.font = "bold 48px serif";
ctx.strokeText('strokeText:给岁月以文明,而不是给文明以岁月', 50, 150, 100)
3、样式属性:textAlign
对齐是相对于 fillText() 方法的 x 值的。例如,如果 textAlign 是 "center",那么该文本的左侧边界会是 x - (textWidth / 2)。
left:文本左对齐。right:文本右对齐。center:文本居中对齐。start:文本对齐界线开始的地方(左对齐指本地从左向右,右对齐指本地从右向左)。end:文本对齐界线结束的地方(左对齐指本地从左向右,右对齐指本地从右向左)。
javascript
const canvas = document.getElementById("canvas");
canvas.width = 350;
const ctx = canvas.getContext("2d");
const x = canvas.width / 2;
// 标注画布的中线
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
ctx.stroke();
// 设置字体样式
ctx.font = "30px serif";
// 展示三种不同的对齐效果
ctx.textAlign = "left";
ctx.fillText("left-aligned", x, 40);
ctx.textAlign = "center";
ctx.fillText("center-aligned", x, 85);
ctx.textAlign = "right";
ctx.fillText("right-aligned", x, 130);
效果如下:

下面的例子演示的是:start和end。虽然英语文本的默认方向是 "ltr",但下面指定了 direction 属性为 "ltr"。
javascript
ctx.font = "30px serif";
ctx.direction = "ltr"; // 从左到右边
ctx.textAlign = "start";
ctx.fillText("Start-aligned", 0, 50);
ctx.textAlign = "end";
ctx.fillText("End-aligned", canvas.width, 120);
效果如下:

4、样式属性:textBaseline
top:文本基线在文本块的顶部。hanging:文本基线是悬挂基线。(用于藏文和其他印度文字。)middle:文本基线在文本块的中间。alphabetic:文本基线是标准的字母基线。默认值。ideographic:文字基线是表意字基线;如果字符本身超出了 alphabetic 基线,那么 ideograhpic 基线位置在字符本身的底部。(用于中文、日文和韩文。)bottom":文本基线在文本块的底部。与 ideographic 基线的区别在于 ideographic 基线不需要考虑下行字母。
javascript
const baselines = [
"top",
"hanging",
"middle",
"alphabetic",
"ideographic",
"bottom",
];
ctx.font = "20px serif";
ctx.strokeStyle = "red";
ctx.beginPath();
ctx.moveTo(0, 100);
ctx.lineTo(840, 100);
ctx.moveTo(0, 55);
ctx.stroke();
baselines.forEach((baseline, index) => {
ctx.save();
ctx.textBaseline = baseline;
let x = index * 120 + 10;
ctx.fillText("Abcdefghijk", x, 100);
ctx.restore();
ctx.fillText(baseline, x + 5, 50);
});
效果如下:

5、样式属性:direction
ltr:文字方向为从左到右。rtl:文字方向为从右到左。inherit:文字方向从相应的 元素或 Document 继承。
javascript
ctx.font = "48px serif";
ctx.fillText("Hi!", 150, 50);
ctx.direction = "rtl";
ctx.fillText("Hi!", 150, 130);
效果如下:

4、图片
相关API:
Image():图片对象;.drawImage:提供了多种在画布(Canvas)上绘制图像的方式。
javascript
// 创建一个图片
const img = new Image()
img.src = 图片地址
/**
* drawImage(image[, sx, sy, sWidth, sHeight], dx, dy[, dWidth, dHeight])
* image:绘制到上下文的元素。允许任何的画布图像源,例如:HTMLImageElement、SVGImageElement、HTMLVideoElement、HTMLCanvasElement、ImageBitmap、OffscreenCanvas 或 VideoFrame。
* sx?:需要绘制到目标上下文中的,源 image 的子矩形(裁剪)的左上角 X 轴坐标。可以使用 3 参数或 5 参数语法来省略这个参数。
* sy?:需要绘制到目标上下文中的,源 image 的子矩形(裁剪)的左上角 Y 轴坐标。可以使用 3 参数或 5 参数语法来省略这个参数。
* sWidth?:需要绘制到目标上下文中的,源 image 的子矩形(裁剪)的宽度。如果不指定,整个矩形(裁剪)从坐标的 sx 和 sy 开始,到 image 的右下角结束。可以使用 3 参数或 5 参数语法来省略这个参数。使用负值将翻转这个图像。
* sHeight?:需要绘制到目标上下文中的,image的矩形(裁剪)选择框的高度。可以使用 3 参数或 5 参数语法来省略这个参数。使用负值将翻转这个图像。
* dx:源 image 的左上角在目标画布上 X 轴坐标。
* dy:源 image 的左上角在目标画布上 Y 轴坐标。
* dWidth?:image 在目标画布上绘制的宽度。允许对绘制的图像进行缩放。如果不指定,在绘制时 image 宽度不会缩放。注意,这个参数不包含在 3 参数语法中。
* dHeight?:image 在目标画布上绘制的高度。允许对绘制的图像进行缩放。如果不指定,在绘制时 image 高度不会缩放。注意,这个参数不包含在 3 参数语法中。
*/
ctx.drawImage('https://mdn.github.io/shared-assets/images/examples/rhino.jpg', 33, 71, 104, 124, 21, 20, 87, 104)

javascript
const img = new Image()
img.src = 'https://mdn.github.io/shared-assets/images/examples/rhino.jpg'
// 图片特别大的话可以等load之后再绘制
img.addEventListener("load", (e) => {
// 不设置宽高,默认显示全部图片
ctx.drawImage(img, 121, 0)
// 设置宽高,图片进行缩放
ctx.drawImage(img, 121, 260, 87, 104)
// 第二个参数和第三个参数是裁剪的图片X、Y轴坐标点,第四个参数和第五个参数是裁剪的长和宽
ctx.drawImage(img, 33, 71, 104, 124, 121, 420, 187, 140)
});
效果如下:

5、填充样式属性/方法
.fillStyle:用于形状内部的颜色、渐变或图案。默认样式为 #000(黑色)。颜色样式有英文颜色单词、哈希、rgb、rgba。一个上下文,如果设置了颜色,那之后画的东西都是这个颜色。.strokeStyle:用于形状描边(轮廓)的颜色、渐变或图案。默认值是 #000(黑色)。.lineWidth:用于设置线宽。一个数字,指定线条的宽度(以坐标空间单位表示)。零、负数、Infinity 和 NaN 值将被忽略。默认值为 1.0。.lineCap:用于指定如何绘制每一条线段的末端。.lineJoin:用于设置 2 个线段如何连接在一起。这个属性在两个连接的线段具有相同方向时没有效果,因为在这种情况下不会添加连接区域。长度为零的退化线段(即所有端点和控制点处于完全相同的位置)也会被忽略。.setLineDash(segments):用于在描线时使用虚线模式。它使用一组值来指定描述模式的线和间隙的交替长度。
javascript
/**
* 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();
});
效果如下:

javascript
/**
* lineJoin演示
* 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();
});
效果如下:

javascript
/**
* setLineDash方法演示,接收一个数组
*/
function drawDashedLine(pattern) {
ctx.beginPath();
ctx.setLineDash(pattern);
ctx.moveTo(0, y);
ctx.lineTo(300, y);
ctx.stroke();
y += 20;
}
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
let y = 15;
drawDashedLine([]); // 空数组为一条实线
drawDashedLine([1, 1]); // 第一个元素为实线长度,第二个元素为间隙长度
drawDashedLine([10, 10]);
drawDashedLine([20, 5]);
drawDashedLine([15, 3, 3, 3]);
drawDashedLine([20, 3, 3, 3, 3, 3, 3, 3]);
drawDashedLine([12, 3, 3]); // 等于 [12, 3, 3, 12, 3, 3],数组元素为奇数时会自动复制一段。
效果如下:

一般使用样式会出现在一个笔画或者是一个图形里。如果在画别的样式的时候,使用.save()和.restore()方法可以将样式恢复到上一个保存的样式。相关API:
.save():用于通过将当前状态放入栈中,以保存 canvas 的完整状态。保存到栈中的绘制状态有当前的变换矩阵、当前的剪切区域、当前的虚线列表、属性值(strokeStyle、fillStyle、globalAlpha、lineWidth、lineCap、lineJoin、miterLimit、lineDashOffset、shadowOffsetX、shadowOffsetY、shadowBlur、shadowColor、globalCompositeOperation、font、textAlign、textBaseline、direction、imageSmoothingEnabled)。.restore():用于通过在绘制状态栈中弹出顶部的条目,将 canvas 恢复到最近的保存状态。如果没有保存状态,此方法不做任何改变。
javascript
// 保存当前状态(这里是保存了一个默认的状态)
ctx.save();
ctx.fillStyle = "green";
ctx.fillRect(10, 10, 100, 100);
// 恢复到最近一次调用 save() 保存的状态(即恢复到默认状态)
ctx.restore();
ctx.fillRect(150, 40, 100, 100);
效果如下:

6、动画相关的介绍
相关API:
.translate(x, y):用于对当前网格添加平移变换。通过在网格上将画布和原点水平移动 x 单位和垂直移动 y 单位,向当前矩阵添加一个平移变换(移动的是坐标轴而不是画板)。.rotate(angle):用于在变换矩阵中增加旋转(旋转的是坐标系而不是画板)。.scale(x, y):用于根据水平和垂直方向,为 canvas 单位添加缩放变换。默认情况下,在 canvas 中一个单位实际上就是一个像素。例如,如果我们将 0.5 作为缩放因子,最终的单位会变成 0.5 像素,并且形状的尺寸会变成原来的一半。相似的方式,我们将 2.0 作为缩放因子,将会增大单位尺寸变成两个像素。形状的尺寸将会变成原来的两倍。设置为负数可以实现水平/垂直翻转。
translate效果:

rotate效果:

变形之后的恢复可以使用.save()和.restore()方法来恢复。

