初识canvas

英文中 Canvas 的意思是“画布”,Canvas 是 HTML5 中新出的一个元素,开发者可以在上面绘制一系列图形。Canvas 在 HTML 文件中的写法很简单:
image.png
canvas 的元素类型是 inline-block 不兼容IE9以下的浏览器。
canvas标签通常需要指定一个id属性(用于js脚本引用),widthheight 属性定义画布的大小。
画布大小设置,使用属性,不使用样式。

  1. <canvas id="myCanvas" width="500" height="500"></canvas>

提示:你可以在HTML页面中使用多个元素。

兼容性处理

HTML5 新增标签不兼容IE9以下的浏览器,当浏览器无法识别元素时,浏览器默认将该元素识别为 行内元素span标签。行内元素没有高度,会导致高度塌陷,影响页面布局。我们的做法是在 canvas 元素外面套一层 div 设置相等的宽高。

  1. <div style="width:500px;height:500px">
  2. <canvas id="myCanvas" width="500" height="500">
  3. 您的浏览器不支持 canvas 元素,请升级至最新版本。
  4. </canvas>
  5. </div>

🖌️上下文对象

canvas 元素的图形绘制 依赖于它的绘图API,它被封装在一个context上下文对象中。

  1. // 获取canvas标签
  2. let mycanvas = document.getElementById('mycanvas');
  3. // 从canvas标签中获取上下文对象
  4. let ctx =mycanvas.getContext('2d');

此处的参数 2d 是指平面绘图,3d 立体绘图的参数为 webgl

我们将 context 上下文对象理解为画笔🖌️。
在图形绘制之前,我们需要设置绘制样式(为画笔调个颜料🎨)。

ctx.fillStyle 填充样式
ctx.strokeStyle 边框样式(线条样式)
ctx.lineWidth 边框宽度(边框样式额外的方法)
  1. ctx.fillStyle = 'red'; // 直接使用颜色名称
  2. ctx.fillStyle = '#EEEEFF'; // 十六进制颜色值
  3. ctx.fillStyle = 'rgb(1, 255, 255)'; // rgb
  4. ctx.fillStyle = 'rgba(1, 255, 255, 0.8)'; // rgba

strokeStyle 同理

矩形

如果只是想绘制一个横平竖直的矩形,可以使用下面两个方法:

  1. // 只描边
  2. ctx.strokeRect(左上角 x 坐标, 左上角 y 坐标, 宽度, 高度);
  3. // 只填充
  4. ctx.fillRect(左上角 x 坐标, 左上角 y 坐标, 宽度, 高度);

以上单位都是像素,不需要px单位,number类型

擦除矩形 clearRect

当我们需要擦除矩形的一部分时,可以使用 ctx.clearRect,它是canvas中唯一一个可以用于清除画布区域的函数,用于擦除画布内容:

  1. ctx.clearRect(左上角 x 坐标, 左上角 y 坐标, 宽度, 高度);
  2. // 清空画布方法:
  3. ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);

圆弧绘制 arc和stroke

绘制圆弧的函数参数比较多:

  1. ctx.arc(x, y, radius, startAngle, endAngle, [anticlockwise]);
  2. // 圆心 x 坐标, 圆心 y 坐标, 半径, 起始角度, 终止角度, [是否为逆时针(true为逆时针,false为顺时针,默认false)]

在 JavaScript 中没有角度的概念,有的是弧度(弧的长度除以弧的半径得出的比值)。

360° = 2π弧度 ; 1弧度 = 360°/2π = 360°/2X31416 = 57.296° ;

image.png

  1. // 1
  2. ctx.arc(250, 250, 100, 0, 1 / 2 * Math.PI);
  3. ctx.stroke();
  4. // 2
  5. ctx.arc(250, 250, 100, 0, 1 / 2 * Math.PI, true);
  6. ctx.stroke();

arc() 只是设置圆弧,并不进行绘图。使用 stroke() 对设置对圆弧进行绘制。

image.png

问题:奇怪的线🧵

  1. // 获取canvas标签
  2. let mycanvas = document.getElementById('mycanvas');
  3. // 从canvas标签中获取上下文对象
  4. let ctx = mycanvas.getContext('2d');
  5. ctx.arc(250, 250, 50, 0, 1 * Math.PI);
  6. ctx.arc(250, 250, 50, 0, 1 * Math.PI, true);
  7. ctx.stroke();

image.png
我们使用两个半圆拼接成一整个圆时,中间会多处一个横线。

即使我们将两个圆弧坐标分开还是一样。

  1. ctx.arc(250, 300, 100, 0, 1 * Math.PI);
  2. ctx.arc(250, 250, 100, 0, 1 * Math.PI, true);

image.png

这是因为:圆弧在 canvas 中是 线条路径 的分类。所有的线条路径会自动连接在一起。当前第一条线条结束时,它的结尾点会自动连接到下一个线条点开始点。

解决:路径闭合 beginPath closePath

如果不想让线条点路径自动连接,就需要手动设置路径闭合

  1. ctx.beginPath(); // 开始路径
  2. ctx.closePath(); // 结束路径

beginPath() 可以理解为从新的路径(或新起点)开始绘制图形,简单来说新图形的绘制过程不受画布已有图案影响。如不设置beginPath(),多次绘制时,已有图案会收到新图案的绘制样式影响。
closePath() 是指闭合路径,即从当前画笔触点至最开始的画笔触点创建连接直线,形成闭合。

所以如果不需要中间的线我们可以这样写⬇️

  1. ctx.beginPath();
  2. ctx.arc(250, 250, 100, 0, 1 * Math.PI);
  3. ctx.stroke();
  4. ctx.beginPath();
  5. ctx.arc(250, 250, 100, 0, 1 * Math.PI, true);
  6. ctx.stroke();

image.png

绘制动画

canvas的动画效果事实是通过周期性计时器setInterval不断清空画布和重新绘制实现。
4.13.gif

  1. window.onload = function () {
  2. // 获取canvas标签
  3. let mycanvas = document.getElementById('mycanvas');
  4. // 从canvas标签中获取上下文对象
  5. let ctx = mycanvas.getContext('2d');
  6. // 定义基础信息,坐标、偏移量
  7. let x = 20,
  8. y = 20,
  9. mx = 2,
  10. my = 3.5;
  11. ctx.fillStyle = 'red';
  12. function cricle(x, y) {
  13. // 在每次绘图之前,清空画布
  14. ctx.clearRect(0, 0, mycanvas.width, mycanvas.height);
  15. ctx.beginPath();
  16. ctx.arc(x, y, 20, 0, 2 * Math.PI);
  17. ctx.fill();
  18. }
  19. setInterval(function () {
  20. // 圆心是20
  21. if (x + mx > mycanvas.width - 20 || x + mx < 20) mx = -mx;
  22. if (y + my > mycanvas.height - 20 || y + my < 20) my = -my;
  23. x += mx;
  24. y += my;
  25. cricle(x, y);
  26. }, 20);
  27. }

绘制线段

  1. // 将画笔移动到某个位置
  2. ctx.moveTo(x,y);
  3. // 从上一个lineTo或moveTo点 绘制线段到本次坐标
  4. ctx.lineTo(x,y);

每次画线都是从 moveTo 的点到 lineTo 的点。
如果没有 moveTo 那么第一次 lineTo 的效果和 moveTo 一样。
每次 lineTo 后如果没有 moveTo ,那么下次 lineTo 的开始点为前一次 lineTo 的结束点。

问题:奇怪的填充机制

我们使用 stroke 绘制一个多边形。

  1. ctx.moveTo(200, 200);
  2. ctx.lineTo(300, 300);
  3. ctx.lineTo(200, 300);
  4. ctx.lineTo(300, 200);
  5. ctx.lineTo(200, 250);
  6. ctx.lineTo(250, 350);
  7. ctx.stroke();

image.png
改用 fill 填充会变成这样,是不是出乎意料了。中间两个闭合区域没有被填充。
image.png
这是因为空白区域进行了两次穿越。
奇数次穿越填充,偶数次穿越不填充。
canvas笔记-填充规则

文本绘制

font 属性:设置文本大小和样式
textBaseline 属性:修改文字垂直方向的对齐方式 对齐的时候是以绘制文本的y作为参照点进行对齐
top / middle / bottom"
textAlign 属性:修改文字水平方向的对齐方式 对齐的时候是以绘制文本的x作为参照点进行对齐
star / center / end
fillText(text, x, y) 方法:绘制实心的文本
strokeText(text, x, y) 方法:绘制空心的文本

image.png

  1. // 1.绘制参考线
  2. ctx.moveTo(0, mycanvas.height / 2);
  3. ctx.lineTo(mycanvas.width, mycanvas.height / 2);
  4. ctx.moveTo(mycanvas.width / 2, 0);
  5. ctx.lineTo(mycanvas.width / 2, mycanvas.height);
  6. ctx.stroke();
  7. // 2.通过 font 属性设置文字的大小和样式
  8. ctx.font = '400px 微软雅黑';
  9. // 3.修改文字垂直方向的对齐方式
  10. ctx.textBaseline = 'middle';
  11. // 4.修改文字水平方向的对齐方式
  12. ctx.textAlign = 'center';
  13. // 5.绘制填充(实体)文本
  14. ctx.fillText('码', mycanvas.width / 2, mycanvas.height / 2);

渐变

线性渐变 createLinearGradient

ctx.createLinearGradient(x1, y1, x2, y2)
通过x1, y1 / x2, y2 确定渐变的方向和渐变的范围

  1. // 1.创建渐变范围
  2. let grd = ctx.createLinearGradient(0, 0, 300, 300); // 线性渐变
  3. // let grd = ctx.createRadialGradient(0, 0, 50, 300, 300, 100); // 径向渐变
  4. // 2.在渐变中添加颜色
  5. // 第一个参数是一个百分比 0~1
  6. // 第二个参数是颜色
  7. grd.addColorStop(0, 'blue');
  8. grd.addColorStop(0.5, 'red');
  9. grd.addColorStop(1, 'green');
  10. // 3.将渐变背景颜色设置给对应的画笔。
  11. ctx.fillStyle = grd;
  12. ctx.fillRect(0, 0, 500, 500);
  13. // 对比矩形
  14. ctx.strokeRect(0, 0, 300, 300);

image.pngimage.png
径向渐变一言难尽……
不难发现,渐变设置的是一个范围渐变 而不是给Rect加上渐变,我们设置 (0, 0), (300, 300) 这个范围的渐变 因此 (300, 300), (500, 500) 之间是没有渐变的

径向渐变 createRadialGradient

ctx.createRadialGradient(x1, y1, r1, x2, y2, r2);

绘制图片 drawImage

Canvas 中绘制图片必须等图片完全加载成功后才能上屏,否则就会绘制失败。

  1. 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. // 1.创建图片
  2. let img = new Image();
  3. // 或 let img = document.createElement('img');
  4. // 2.加载图片
  5. img.src = './一张图.jpg';
  6. // 3.图片完全加载后进行绘制
  7. img.onload = function () {
  8. // 4.将加载的图片绘制的canvas上
  9. ctx.drawImage(img, 0, 0);
  10. }

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 的原点坐标。

  1. ctx.fillStyle = 'red';
  2. ctx.fillRect(0, 0, 100, 100);
  3. ctx.translate(100, 100); // 位移 100, 100 可以理解为 将画笔移动到坐标100 100
  4. ctx.fillStyle = 'yellow';
  5. ctx.fillRect(0, 0, 100, 100);
  6. ctx.translate(100, 100); // 多次使用位移 效果是叠加的
  7. ctx.fillStyle = 'blue';
  8. ctx.fillRect(0, 0, 100, 100);

image.png image.png

缩放 scale

用于缩放画布的x轴和y轴的坐标。

  1. // 参照线
  2. ctx.strokeRect(500, 500, -450, -450);
  3. ctx.fillStyle = 'red';
  4. ctx.fillRect(0, 0, 100, 100);
  5. ctx.scale(.5, .5);
  6. ctx.fillStyle = 'yellow';
  7. ctx.fillRect(100, 100, 100, 100);
  8. ctx.scale(.5, .5);
  9. ctx.fillStyle = 'blue';
  10. ctx.fillRect(200, 200, 100, 100);

scale(x, y) 参数表示缩放比例,类型是 number ,1=100%(不缩放),0.5=50%,2=200%,依次类推。

image.png image.png
image.png

旋转 rotate

用于以原点为中心旋转 canvas 。

  1. ctx.fillRect(200, 200, 100, 100);
  2. ctx.rotate(45 * Math.PI / 180);
  3. ctx.fillRect(200, 200, 100, 100);

image.png

自定义变形 transform

  1. context.transform(a, b, c, d, e, f);
  2. a: 水平缩放
  3. b: 水平倾斜
  4. c: 垂直倾斜
  5. d: 垂直缩放
  6. e: 水平位移
  7. f: 垂直位移
  8. 初时状态是:context.transform(1, 0, 0, 1, 0, 0);
  9. 可用于初始化变形。

变形案例:文字倒影

  1. // 位移 200, 200
  2. ctx.translate(200, 200);
  3. // 创建一段文本
  4. ctx.font = '30px 微软雅黑';
  5. ctx.fillStyle = 'orange';
  6. ctx.textAlign = 'center';
  7. ctx.fillText('你好世界', 0, 0);
  8. // 垂直缩放设置为负数,实现文字上线颠倒🙃️
  9. ctx.transform(1, 0, -1.5, -.7, 5, 5);
  10. // 倒影文本
  11. ctx.fillStyle = 'red';
  12. ctx.fillText('你好世界', 0, 0);

image.png

变形状态保存恢复

用于保存和恢复变形状态

  1. ctx.save(); // 保存
  2. ctx.restore(); // 恢复

Canvas的状态存储在栈中,每次当 save() 方法被调用后,当前的状态就会被推送到栈中保存。类似于浏览器的历史记录功能。

每当遇到 save() 时就相当于吧当前的状态放到栈中,遇到 restore() 时相当于从栈中拿出来一个。

案例:

⏰时钟

image.png
时钟源码

🐍贪吃蛇

贪吃蛇.gif
贪吃蛇源码

🖌️自定义画板

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

拓展

来自卖好车大前端团队分享 —— canvas基础之图片处理