【笔记】Canvas学习笔记

前言

Canvas标签可以通过Javascript来绘制图形

创建一个Canvas标签

  • 兼容性问题,Canvas内部文字只有不支持的浏览器才会看到,主要针对IE6、IE7、IE8
1
<canvas>当前浏览器版本不支持,请升级浏览器</canvas>

属性

  • 画布宽高
  • 不要使用CSS设置宽高,CSS设置宽高会拉伸画布,使画布失真
1
<canvas width="" height=""></canvas>

绘制上下文对象

2d:上下文有两个,分别是2d和3d

1
2
let canvas = document.querySelector("canvas");
let ctx = canvas.getContext("2d");

Canvas的像素化

  • 我们使用Canvas绘制了一个图形,Canvas就会像素化图形,Canvas没有能力从画布上再次更改已经在画布上的内容,这个就是Canvas比较轻量的原因

Canvas的动画思想

  • 如果我们想要让Canvas绘制的图形移动,必须按照清屏->更新->渲染的逻辑进行编程,总之就是重新再画一次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 得到canvas画布
let canvas = document.querySelector("canvas");
// 得到画布的上下文,上下文有两个,2d的上下文和3d的上下文
let ctx = canvas.getContext("2d");
// 信号量
let left = 100;
// 动画过程
setInterval(function() {
// 清除画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 更新信号量
left += 1;
// 绘制矩形
ctx.fillRect(left, 100, 100, 100);
}, 30);

面向对象实现Canvas动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 得到canvas画布
let canvas = document.querySelector("canvas");
// 得到画布的上下文,上下文有两个,2d的上下文和3d的上下文
let ctx = canvas.getContext("2d");
// 构造函数
function Rect(x, y, w, h, color) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.color = color;
}
// 更新函数
Rect.prototype.update = function() {
this.x += 1;
};
// 渲染函数
Rect.prototype.render = function() {
// 设置颜色
ctx.fillStyle = this.color;
// 渲染
ctx.fillRect(this.x, this.y, this.w, this.h);
};
// 实例化
let r1 = new Rect(100, 100, 50, 50, "black");
// 动画过程
setInterval(function() {
// 清屏
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 调用更新函数
r1.update();
// 调用渲染函数
r1.render();
}, 10);

Canvas的API

填充图形

设置颜色

  • 颜色要先设置,再绘制图形

<color>:颜色名称

1
ctx.fillStyle = "<color>";

填充矩形

<x>:图形距离原点(左上角)的横坐标
<y>:图形距离原点(左上角)的纵坐标
<w>:矩形的长
<h>:矩形的宽

1
ctx.fillRect(<x>, <y>, <w>, <h>);

绘制图形

设置颜色

1
ctx.strokeStyle = "<color>";

绘制矩形

  • 绘制矩形边框
1
ctx.strokeRect(<x>, <y>, <w>, <h>);

清屏

<w>:清屏的长范围
<d>:清屏的宽范围

1
ctx.clearRect(<x>, <y>, <w>, <h>);

绘制路径

  • 绘制路径目的是设置一个不规则的多边形状态,路径都是闭合的,使用路径进行绘制的时候需要既定的步骤
  1. 设置路径的起点
  2. 使用画图命令,画出路径
  3. 关闭封闭路径
  4. 填充或者绘制已经封闭路径的形状
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 创建一个路径
ctx.beginPath();
// 移动绘制点
ctx.moveTo(<x>, <y>);
// 描述行进路径
ctx.lineTo(<x>, <y>);
ctx.lineTo(<x>, <y>); // 行进路径可以多次描述
// 封闭路径
ctx.closePath();
// 绘制不规则图形
ctx.strokeStyle = "<color>"; // 设置颜色(可选)
ctx.stroke();
// 填充图形(可选)
ctx.fillStyle = "<color>"; // 设置颜色(可选)
ctx.fill();
  • 自动封闭

对于stroke,如果不使用closePath()封闭绘制,则边框不会自动封闭
对于fill,如果不使用closePath()封闭填充,则图形会自动封闭

圆弧

<x>:圆心横坐标
<y>:圆心纵坐标
<radius>:半径
<startAngle>:开始位置,如果是数字表示圆的弧度
<endAngle>:结束位置,如果是数字表示圆的弧度,值为2*Math.PI2*3.147时表示一个完整的圆
anticlockwise:顺时针或逆时针状态,true表示顺时针,false表示逆时针

1
2
3
4
5
6
7
8
ctx.beginPath();
ctx.art(<x>, <y>, <radius>, <startAngle>, <endAngle>, <anticlockwise>);
// 绘制不规则图形
ctx.strokeStyle = "<color>"; // 设置颜色(可选)
ctx.stroke();
// 填充图形(可选)
ctx.fillStyle = "<color>"; // 设置颜色(可选)
ctx.fill();

设置透明度

<num>:1~0之间的数,1为不透明,0为透明

1
ctx.globalAlpha = <num>;

线型

设置粗细

<num>:粗细值,默认为1.0,表示1像素

1
ctx.lineWidth = <num>;

设置末端的形状

<value>

butt:缺省值,末端以方形结束
round:末端以圆形结束
square:末端以方形结束,但是增加了一个__宽度是线段相同,高度是线段厚度一半__的矩形区域

1
ctx.lineCap = "<value>";

设置相连部分的形状

<value>

round:相连部分为圆角
bevel:相连部分为平角
miter:缺省值,相连部分为尖角

1
ctx.line = "<value>";

设置虚线

<value>

[a,b]

a:线的长度
b:空的距离

1
ctx.setLineDash(<value>);

起始偏移量

<value>:偏移量值

1
ctx.lineDashOffset = <value>;

绘制文本

设置画笔

<size>:字号,单位px
<family> :字体名

1
ctx.font = "<size> <family>";

绘制文本

<str>:文本内容
<x>:文本位置横坐标
<x>:文本位置纵坐标

1
ctx.fillText(<str>, <x>, <y>);

设置文本对齐方式

<value>

left:基于x轴左对齐
right:基于x轴右对齐
center:基于x轴居中
start:缺省值,基于x轴起始对齐
end:基于x轴末尾对齐

1
ctx.textAlign = "<value>";

渐变

线性渐变

<x_1>:开始点横坐标
<y_1>:开始点纵坐标
<x_2>:结束点横坐标
<y_2>:结束点纵坐标

<num>:1~0之间的数
<color>:颜色值字符串

1
2
3
4
5
let linear = ctx.createLinearGradient(<x_1>, <y_1>, <x_2>, <y_2>);
linear.addColorStop(<num>, <color>);
linear.addColorStop(<num>, <color>);
// 将颜色赋值给画笔
ctx.fillStyle = linear;

径像渐变

<x_1>:开始圆心横坐标
<y_1>:开始圆心纵坐标
<r_1>:开始圆的半径
<x_2>:结束圆心横坐标
<y_2>:结束圆心纵坐标
<r_2>:结束圆的半径

1
2
3
4
5
let linear = ctx.createRadialGradient(<x_1>, <y_1>, <r_1>, <x_2>, <y_2>, <r_2>);
linear.addColorStop(<num>, <color>);
linear.addColorStop(<num>, <color>);
// 将颜色赋值给画笔
ctx.fillStyle = linear;

阴影

<x>:阴影水平方向偏移量,默认值为0,负数向左,正数向右
<y>:阴影垂直方向偏移量,默认值为0,负数向上,正数向下
<num>:模糊效果,默认值为0
<color>:阴影的颜色

1
2
3
4
ctx.shadowOffsetX = <x>;
ctx.shadowOffsetY = <y>;
ctx.shadowBlur = <num>;
ctx.shadowColor = "<color>";

使用图片

  • Canvas使用dradImage来绘制图片,主要是把外部图片导入进来绘制到画布上

两个参数

<src>:图片路径
<x>:图片放置横坐标
<y>:图片放置纵坐标

1
2
3
4
5
let image = new Image();
image.src = "<src>";
image.onload = function() {
ctx.drawImage(image, <x>, <y>);
}

四个参数

<width>:图片宽度
<height>:图片高度

1
2
3
4
5
let image = new Image();
image.src = "<src>";
image.onload = function() {
ctx.drawImage(image, <x>, <y>, <width>, <height>);
}

八个参数

<cut_x>:裁切图片的位置横坐标
<cut_y>:裁切图片的位置纵坐标
<cut_width>:裁切的宽度
<cut_height>:裁切的高度
<slice_x>:切片放置的位置横坐标
<slice_y>:切片放置的位置纵坐标
<slice_width>:切片形变后的宽度
<slice_height>:切片形变后的高度

1
2
3
4
5
let image = new Image();
image.src = "<src>";
image.onload = function() {
ctx.drawImage(image, <cut_x>, <cut_y>, <cut_width>, <cut_height>, <slice_x>, <slice_y>, <slice_width>, <slice_height>);
}

资源管理器

  • 当游戏需要的图片资源全部加载完毕的时候再开始游戏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
function Game() {

this.ctx = document.querySelector("canvas").getContext("2d");

// 将资源地址保存在R属性中
this.R = {
"wp01": "./img/WallPaper01.jpg",
"wp02": "./img/WallPaper02.jpg",
"wp03": "./img/WallPaper03.jpg",
"wp04": "./img/WallPaper04.jpg"
}

// 获取资源图片的总数
let len = Object.keys(this.R).length;

// 定义计数器,用于计数加载完毕的资源数
let count = 0;

// 将R中的所有图片地址重新赋值为Image对象
for (let k in this.R) {
// 创建一个Image对象
let img = new Image();
// 备份图片地址到Image对象的src属性中
img.src = this.R[k];
// 把图片对象重新存放在R属性中
this.R[k] = img;
}

// 遍历所有Image对象,并加载
for (let k in this.R) {
// 为了在内部继续使用this,所以将this定义为变量
let self = this;
// 加载图片
this.R[k].onload = function () {
// 加载完图片,计数器+1
count += 1;

// 清屏
self.ctx.clearRect(0, 0, 600, 400);
// 在页面渲染加载进度
self.ctx.font = "20px 苹方";
self.ctx.fillText(`图片已经加载${count}/${len}`, 100, 100);

// 判断是否加载游戏,如果加载完了,开始游戏
if (count == len) {
self.ctx.clearRect(0, 0, 600, 400);
self.ctx.font = "20px 苹方";
self.ctx.fillText(`开始游戏`, 100, 100);
}

}
}

}

Game();

变形

状态

  • 每一次保存,可以将当前画笔状态创建快照
  • 每一次恢复,可以还原上一次快照的画笔状态
  • 可以进行多次保存,创建多个快照,多次保存之后可以进行多次恢复,按顺序恢复上一次快照
保存画布状态
1
ctx.save();
恢复画布状态
1
ctx.restore();

平移

<x>:横坐标偏移量
<y>:纵坐标偏移量

1
ctx.translate(<x>, <y>);

旋转

<num>:旋转的角度,0~1之间的数

1
ctx.rotate(<num>);

缩放

<x>:x轴缩放的倍数,01直接的数表示缩小,大于的数表示放大
<y>:y轴缩放的倍数,0
1直接的数表示缩小,大于的数表示放大

1
ctx.scale(<x>, <y>);

综合属性

  • 平移、旋转、缩放的综合写法

<zoom_x>:水平方向缩放
<zoom_y>:垂直方向缩放
<tilt_x>:水平方向倾斜
<tilt_y>:垂直方向倾斜
<move_x>:水平方向移动
<move_y>:垂直方向移动

1
ctx.transform(<zoom_x>, <tilt_x>, <tilt_y>, <zoom_y>, <move_x>, <move_y>)

合成

  • 实际上就是蒙版压盖

<value>

destination-in:只显示旧图形∈新图形
destination-over:将新图形绘制在旧图形的底部
destination-out:只显示旧图形∉新图形
destination-atop:新图形绘制在旧图形底部的同时,只显示新图形的部分
source-in:只显示新图形∩旧图形
source-over:缺省值,将新图形绘制在旧图形的顶部
source-out:只显示新图形∉旧图形
source-atop:只显示非新图形∉旧图形

1
ctx.globalCompositeOperation = "<value>";

完成

参考文献

哔哩哔哩——web前端小清风