初识canvas
英文中 Canvas 的意思是“画布”,Canvas 是 HTML5 中新出的一个元素,开发者可以在上面绘制一系列图形。Canvas 在 HTML 文件中的写法很简单:
canvas 的元素类型是 inline-block 不兼容IE9以下的浏览器。
canvas标签通常需要指定一个id属性(用于js脚本引用),width 和 height 属性定义画布的大小。
画布大小设置,使用属性,不使用样式。
<canvas id="myCanvas" width="500" height="500"></canvas>
提示:你可以在HTML页面中使用多个
兼容性处理
HTML5 新增标签不兼容IE9以下的浏览器,当浏览器无法识别元素时,浏览器默认将该元素识别为 行内元素 即span标签。行内元素没有高度,会导致高度塌陷,影响页面布局。我们的做法是在 canvas 元素外面套一层 div 设置相等的宽高。
<div style="width:500px;height:500px"><canvas id="myCanvas" width="500" height="500">您的浏览器不支持 canvas 元素,请升级至最新版本。</canvas></div>
🖌️上下文对象
canvas 元素的图形绘制 依赖于它的绘图API,它被封装在一个context上下文对象中。
// 获取canvas标签let mycanvas = document.getElementById('mycanvas');// 从canvas标签中获取上下文对象let ctx =mycanvas.getContext('2d');
此处的参数 2d 是指平面绘图,3d 立体绘图的参数为 webgl
我们将 context 上下文对象理解为画笔🖌️。
在图形绘制之前,我们需要设置绘制样式(为画笔调个颜料🎨)。
| ctx.fillStyle | 填充样式 |
|---|---|
| ctx.strokeStyle | 边框样式(线条样式) |
| ctx.lineWidth | 边框宽度(边框样式额外的方法) |
ctx.fillStyle = 'red'; // 直接使用颜色名称ctx.fillStyle = '#EEEEFF'; // 十六进制颜色值ctx.fillStyle = 'rgb(1, 255, 255)'; // rgbctx.fillStyle = 'rgba(1, 255, 255, 0.8)'; // rgba
strokeStyle 同理
矩形
如果只是想绘制一个横平竖直的矩形,可以使用下面两个方法:
// 只描边ctx.strokeRect(左上角 x 坐标, 左上角 y 坐标, 宽度, 高度);// 只填充ctx.fillRect(左上角 x 坐标, 左上角 y 坐标, 宽度, 高度);
以上单位都是像素,不需要px单位,number类型
擦除矩形 clearRect
当我们需要擦除矩形的一部分时,可以使用 ctx.clearRect,它是canvas中唯一一个可以用于清除画布区域的函数,用于擦除画布内容:
ctx.clearRect(左上角 x 坐标, 左上角 y 坐标, 宽度, 高度);// 清空画布方法:ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
圆弧绘制 arc和stroke
绘制圆弧的函数参数比较多:
ctx.arc(x, y, radius, startAngle, endAngle, [anticlockwise]);// 圆心 x 坐标, 圆心 y 坐标, 半径, 起始角度, 终止角度, [是否为逆时针(true为逆时针,false为顺时针,默认false)]
在 JavaScript 中没有角度的概念,有的是弧度(弧的长度除以弧的半径得出的比值)。
360° = 2π弧度 ; 1弧度 = 360°/2π = 360°/2X31416 = 57.296° ;

// 1ctx.arc(250, 250, 100, 0, 1 / 2 * Math.PI);ctx.stroke();// 2ctx.arc(250, 250, 100, 0, 1 / 2 * Math.PI, true);ctx.stroke();
arc() 只是设置圆弧,并不进行绘图。使用 stroke() 对设置对圆弧进行绘制。

问题:奇怪的线🧵
// 获取canvas标签let mycanvas = document.getElementById('mycanvas');// 从canvas标签中获取上下文对象let ctx = mycanvas.getContext('2d');ctx.arc(250, 250, 50, 0, 1 * Math.PI);ctx.arc(250, 250, 50, 0, 1 * Math.PI, true);ctx.stroke();

我们使用两个半圆拼接成一整个圆时,中间会多处一个横线。
即使我们将两个圆弧坐标分开还是一样。
ctx.arc(250, 300, 100, 0, 1 * Math.PI);ctx.arc(250, 250, 100, 0, 1 * Math.PI, true);

这是因为:圆弧在 canvas 中是 线条路径 的分类。所有的线条路径会自动连接在一起。当前第一条线条结束时,它的结尾点会自动连接到下一个线条点开始点。
解决:路径闭合 beginPath closePath
如果不想让线条点路径自动连接,就需要手动设置路径闭合
ctx.beginPath(); // 开始路径ctx.closePath(); // 结束路径
beginPath() 可以理解为从新的路径(或新起点)开始绘制图形,简单来说新图形的绘制过程不受画布已有图案影响。如不设置beginPath(),多次绘制时,已有图案会收到新图案的绘制样式影响。closePath() 是指闭合路径,即从当前画笔触点至最开始的画笔触点创建连接直线,形成闭合。
所以如果不需要中间的线我们可以这样写⬇️
ctx.beginPath();ctx.arc(250, 250, 100, 0, 1 * Math.PI);ctx.stroke();ctx.beginPath();ctx.arc(250, 250, 100, 0, 1 * Math.PI, true);ctx.stroke();

绘制动画
canvas的动画效果事实是通过周期性计时器setInterval不断清空画布和重新绘制实现。
window.onload = function () {// 获取canvas标签let mycanvas = document.getElementById('mycanvas');// 从canvas标签中获取上下文对象let ctx = mycanvas.getContext('2d');// 定义基础信息,坐标、偏移量let x = 20,y = 20,mx = 2,my = 3.5;ctx.fillStyle = 'red';function cricle(x, y) {// 在每次绘图之前,清空画布ctx.clearRect(0, 0, mycanvas.width, mycanvas.height);ctx.beginPath();ctx.arc(x, y, 20, 0, 2 * Math.PI);ctx.fill();}setInterval(function () {// 圆心是20if (x + mx > mycanvas.width - 20 || x + mx < 20) mx = -mx;if (y + my > mycanvas.height - 20 || y + my < 20) my = -my;x += mx;y += my;cricle(x, y);}, 20);}
绘制线段
// 将画笔移动到某个位置ctx.moveTo(x,y);// 从上一个lineTo或moveTo点 绘制线段到本次坐标ctx.lineTo(x,y);
每次画线都是从 moveTo 的点到 lineTo 的点。
如果没有 moveTo 那么第一次 lineTo 的效果和 moveTo 一样。
每次 lineTo 后如果没有 moveTo ,那么下次 lineTo 的开始点为前一次 lineTo 的结束点。
问题:奇怪的填充机制
我们使用 stroke 绘制一个多边形。
ctx.moveTo(200, 200);ctx.lineTo(300, 300);ctx.lineTo(200, 300);ctx.lineTo(300, 200);ctx.lineTo(200, 250);ctx.lineTo(250, 350);ctx.stroke();

改用 fill 填充会变成这样,是不是出乎意料了。中间两个闭合区域没有被填充。
这是因为空白区域进行了两次穿越。
奇数次穿越填充,偶数次穿越不填充。
canvas笔记-填充规则
文本绘制
| font | 属性:设置文本大小和样式 | |
|---|---|---|
| textBaseline | 属性:修改文字垂直方向的对齐方式 | 对齐的时候是以绘制文本的y作为参照点进行对齐 “ top / middle / bottom" |
| textAlign | 属性:修改文字水平方向的对齐方式 | 对齐的时候是以绘制文本的x作为参照点进行对齐 “ star / center / end“ |
| fillText(text, x, y) | 方法:绘制实心的文本 | |
| strokeText(text, x, y) | 方法:绘制空心的文本 |

// 1.绘制参考线ctx.moveTo(0, mycanvas.height / 2);ctx.lineTo(mycanvas.width, mycanvas.height / 2);ctx.moveTo(mycanvas.width / 2, 0);ctx.lineTo(mycanvas.width / 2, mycanvas.height);ctx.stroke();// 2.通过 font 属性设置文字的大小和样式ctx.font = '400px 微软雅黑';// 3.修改文字垂直方向的对齐方式ctx.textBaseline = 'middle';// 4.修改文字水平方向的对齐方式ctx.textAlign = 'center';// 5.绘制填充(实体)文本ctx.fillText('码', mycanvas.width / 2, mycanvas.height / 2);
渐变
线性渐变 createLinearGradient
ctx.createLinearGradient(x1, y1, x2, y2)
通过x1, y1 / x2, y2 确定渐变的方向和渐变的范围
// 1.创建渐变范围let grd = ctx.createLinearGradient(0, 0, 300, 300); // 线性渐变// let grd = ctx.createRadialGradient(0, 0, 50, 300, 300, 100); // 径向渐变// 2.在渐变中添加颜色// 第一个参数是一个百分比 0~1// 第二个参数是颜色grd.addColorStop(0, 'blue');grd.addColorStop(0.5, 'red');grd.addColorStop(1, 'green');// 3.将渐变背景颜色设置给对应的画笔。ctx.fillStyle = grd;ctx.fillRect(0, 0, 500, 500);// 对比矩形ctx.strokeRect(0, 0, 300, 300);


径向渐变一言难尽……
不难发现,渐变设置的是一个范围渐变 而不是给Rect加上渐变,我们设置 (0, 0), (300, 300) 这个范围的渐变 因此 (300, 300), (500, 500) 之间是没有渐变的
径向渐变 createRadialGradient
ctx.createRadialGradient(x1, y1, r1, x2, y2, r2);
绘制图片 drawImage
Canvas 中绘制图片必须等图片完全加载成功后才能上屏,否则就会绘制失败。
ctx.drawImage('./一张图.jpg', 0, 0);
:::danger Uncaught TypeError: Failed to execute ‘drawImage’ on ‘CanvasRenderingContext2D’: The provided value is not of type ‘(CSSImageValue or HTMLCanvasElement or HTMLImageElement or HTMLVideoElement or ImageBitmap or OffscreenCanvas or SVGImageElement or VideoFrame)’. :::
因此正确的写法是:
绘制图片需要在图片加载完毕后执行
// 1.创建图片let img = new Image();// 或 let img = document.createElement('img');// 2.加载图片img.src = './一张图.jpg';// 3.图片完全加载后进行绘制img.onload = function () {// 4.将加载的图片绘制的canvas上ctx.drawImage(img, 0, 0);}
drawImage参数补充
如果只有三个参数, 那么第一个参数就是需要绘制的图片,后面的两个参数是指定图片从什么位置开始绘制
| ctx.drawImage(img对象, x, y) | 如果只有三个参数, 那么第一个参数就是需要绘制的图片,后面的两个参数是指定图片从什么位置开始绘制 |
|---|---|
| ctx.drawImage(img对象, x, y, w, h) | 如果只有五个参数, 那么第一个参数就是需要绘制的图片,第二和第三个参数是指定图片从什么位置开始绘制,第四、第五两个参数是指定图片需要拉伸到多大 |
| ctx.drawImage(img对象, 裁切x, 裁切y, 裁切w, 裁切h, 绘制x, 绘制y, 绘制w, 绘制h) | 如果有九个参数, 那么第一个参数就是需要绘制的图片,第2~3个参数指定图片上定位的位置,第4~5个参数指定从定位的位置开始截取多大的图片,第6~7个参数指定图片从什么位置开始绘制,最后的两个参数是指定图片需要拉伸到多大 |
变形
在 canvas 中所有的变形属性操作的都是坐标系,而不是图形!!!并且效果都是叠加的
位移 translate
用于移动 canvas 的原点坐标。
ctx.fillStyle = 'red';ctx.fillRect(0, 0, 100, 100);ctx.translate(100, 100); // 位移 100, 100 可以理解为 将画笔移动到坐标100 100ctx.fillStyle = 'yellow';ctx.fillRect(0, 0, 100, 100);ctx.translate(100, 100); // 多次使用位移 效果是叠加的ctx.fillStyle = 'blue';ctx.fillRect(0, 0, 100, 100);

缩放 scale
用于缩放画布的x轴和y轴的坐标。
// 参照线ctx.strokeRect(500, 500, -450, -450);ctx.fillStyle = 'red';ctx.fillRect(0, 0, 100, 100);ctx.scale(.5, .5);ctx.fillStyle = 'yellow';ctx.fillRect(100, 100, 100, 100);ctx.scale(.5, .5);ctx.fillStyle = 'blue';ctx.fillRect(200, 200, 100, 100);
scale(x, y) 参数表示缩放比例,类型是 number ,1=100%(不缩放),0.5=50%,2=200%,依次类推。
旋转 rotate
用于以原点为中心旋转 canvas 。
ctx.fillRect(200, 200, 100, 100);ctx.rotate(45 * Math.PI / 180);ctx.fillRect(200, 200, 100, 100);
自定义变形 transform
context.transform(a, b, c, d, e, f);a: 水平缩放b: 水平倾斜c: 垂直倾斜d: 垂直缩放e: 水平位移f: 垂直位移初时状态是:context.transform(1, 0, 0, 1, 0, 0);可用于初始化变形。
变形案例:文字倒影
// 位移 200, 200ctx.translate(200, 200);// 创建一段文本ctx.font = '30px 微软雅黑';ctx.fillStyle = 'orange';ctx.textAlign = 'center';ctx.fillText('你好世界', 0, 0);// 垂直缩放设置为负数,实现文字上线颠倒🙃️ctx.transform(1, 0, -1.5, -.7, 5, 5);// 倒影文本ctx.fillStyle = 'red';ctx.fillText('你好世界', 0, 0);

变形状态保存恢复
用于保存和恢复变形状态
ctx.save(); // 保存ctx.restore(); // 恢复
Canvas的状态存储在栈中,每次当 save() 方法被调用后,当前的状态就会被推送到栈中保存。类似于浏览器的历史记录功能。
每当遇到 save() 时就相当于吧当前的状态放到栈中,遇到 restore() 时相当于从栈中拿出来一个。
案例:
⏰时钟
🐍贪吃蛇
🖌️自定义画板

自定义画板源码
存在bug
点击画布 移动鼠标 鼠标移到画布外 松开鼠标 再移回画布 此时画的图形 会在下次绘画时消失



