canvas
# canvas
参考教程:https://www.hangge.com/blog/cache/search.php?key=canvas&page=1
- 只有两个属性:width、height
- 如果你绘制出来的图像是扭曲的, 尝试用width和height属性为
[TOC]
# 一、基本认识
# 1.1 不支持的浏览器可替换内容
<canvas id="stockGraph" width="150" height="150">
current stock price: $3.15 +0.15
</canvas>
<canvas id="clock" width="150" height="150">
<img src="images/clock.png" width="150" height="150" alt=""/>
</canvas>
2
3
4
5
6
7
# 1.1.2 检查支持性
通过简单的测试getContext()方法的存在,脚本可以检查编程支持性。
var canvas = document.getElementById('tutorial');
if (canvas.getContext){
var ctx = canvas.getContext('2d');
// drawing code here
} else {
// canvas-unsupported code here
}
2
3
4
5
6
7
8
# 1.2 与SVG的区别
- Canvas
- 依赖分辨率
- 不支持事件绑定
- 最合适网页游戏
- SVG
- 不依赖分辨率
- 支持事件绑定
- 大型渲染区域的程序(例如百度地图)
- 不能用来实现网页游戏
# 二、基本绘制
# 2.1 绘制矩形
- fillRect(x, y, width, height)
- 绘制一个填充的矩形
- strokeRect(x, y, width, height)
- 绘制一个矩形的边框
- clearRect(x, y, width, height)
- 清除指定矩形区域,让清除部分完全透明。
# 2.2 绘制路径
- beginPath()
- 新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径。
- closePath()
- 闭合路径之后图形绘制命令又重新指向到上下文中。
- stroke()
- 通过线条来绘制图形轮廓。
- 调用stroke()时不会自动闭合。
- fill()
- 通过填充路径的内容区域生成实心的图形。
- 调用fill()函数时,所有没有闭合的形状都会自动闭合,不需要调用closePath()函数。
# 2.3 圆弧
绘制圆弧或者圆,我们使用arc()方法。
arcTo()亦可,但并不可靠。
arc(x, y, radius, startAngle, endAngle, anticlockwise)
- 画一个以(x,y)为圆心的以radius为半径的圆弧(圆)
- 从startAngle开始到endAngle结束,按照anticlockwise给定的方向(默认为顺时针)来生成。
- sAngle 起始角,以弧度计。(弧的圆形的三点钟位置是 0 度)。eAngle 结束角,以弧度计。
- anticlockwise可选。规定应该逆时针还是顺时针绘图。False = 顺时针,true = 逆时针。
注意:arc()函数中的角度单位是弧度,不是度数。
- 角度与弧度的js表达式:radians=(Math.PI/180)*degrees。
# 2.4 插入图片
- 等图片加载完,再执行canvas操作
- 图片预加载:在onload中调用方法
// oImg: 当前图片
// x,y: 坐标
// w,h: 宽高
drawImage(oImg,x,y,w,h)
2
3
4
# 2.5 设置背景
createPattern(oImg,平铺方式)
// repeat、repeat-x、repeat-y、no-repeat
2
# 2.6 渐变色
// 添加渐变线
let grd1 = ctx.createLinearGradient(0, 300, 0, 0);
// 添加颜色断点
grd1.addColorStop(0, "rgba(10, 102, 202, 1)");
grd1.addColorStop(0.25, "rgba(10, 102, 202, 1)");
grd1.addColorStop(0.5, "rgba(10, 102, 202, 1)");
grd1.addColorStop(0.75, "rgba(33, 150, 243, 1)");
grd1.addColorStop(1, "rgba(33, 150, 243, 1)");
2
3
4
5
6
7
8
# 2.7 贝塞尔曲线
参考教程:https://www.jianshu.com/p/18a495956b15
# 2.8 save 与 restore
参考教程:https://juejin.im/post/5d1c577f6fb9a07ecd3d78ae
CanvasRenderingContext2D渲染环境包含了多种绘图的样式状态(属性有线的样式、填充样式、阴影样式、文本样式)
# 三、小案例
# 3.1 鼠标绘制
<script>
window.onload = function () {
var oCan = document.getElementById('can');
var oM = oCan.getContext('2d'); //如果是3d绘图则是 wdbg1 ,但是会存在兼容问题
//开始绘制
function draw() {
oCan.onmousedown = function (ev) {
var ev = ev || window.event;
oM.moveTo(ev.clientX - oCan.offsetLeft, ev.clientY - oCan.offsetTop);
document.onmousemove = function (ev) {
var ev = ev || window.event;
oM.lineTo(ev.clientX - oCan.offsetLeft, ev.clientY - oCan.offsetTop);
oM.stroke();;
};
document.onmouseup = function () {
document.onmousemove = document.onmouseup = null;
};
};
}
draw();
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<body>
<!-- 这里有个小问题,如果不是内联样式的话,则样式是按照默认的宽300px,高150px的比例写的。-->
<!-- 比如这里写在外部的话就会变成高是宽的两倍 -->
<canvas id="can" width="400" height="400">
<!-- 如果不支持的话就会显示这里面的内容 -->
<span>该浏览器不支持canvas</span>
</canvas>
</body>
2
3
4
5
6
7
8
# 3.2 方块移动
window.onload = function () {
var oCan = document.getElementById('can');
var oR = oCan.getContext('2d');
var num = 0; //用来规定方块的起始位置
var come = 1; //记录方块移动的方向
//规定填充的颜色
oR.fillStyle = 'yellow';
//规定边框绘制的颜色
oR.strokeStyle = 'red';
//规定边框的宽度
oR.lineWidth = 10;
//开个定时器让方块动起来
setInterval(function () {
//避免上一个绘制的矩形还留在画布上
//清除整个画布(起始点X,起始点Y,宽,高)
oR.clearRect(0, 0, oCan.width, oCan.height);
//绘制矩形(起始点X,起始点Y,宽,高)
oR.strokeRect(num, num, 100, 100);
//绘制矩形(起始点X,起始点Y,宽,高)
oR.fillRect(num, num, 100, 100);
if(num<300&&come==1){
num++;
}else{
come=0;
};
if(num>0 && come==0){
num--;
}else{
come=1;
}
}, 5);
}
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
# 3.3 时钟
window.onload = function () {
var oCan = document.getElementById('can');
var oCir = oCan.getContext('2d');
function toDraw() {
//钟表的中心位置
var x = 200;
var y = 200;
//钟表的半径
var r = 150;
oCir.clearRect(0, 0, oCan.width, oCan.height);
//获取当前时间
var oDate = new Date();
var oHour = oDate.getHours();
var oMin = oDate.getMinutes();
var oSec = oDate.getSeconds();
//绘制分的刻度
//旋转的角度
//-90是为了让起始点从三点方向回到十二点方向
var oHourValue = (-90 + oHour * 30 + oMin / 2) * Math.PI / 180;
var oMinValue = (-90 + oMin * 6) * Math.PI / 180;
var oSecValue = (-90 + oSec * 6) * Math.PI / 180;
//起始一条路径,或重置当前路径
oCir.beginPath();
for (var i = 0; i < 60; i++) {
oCir.moveTo(x, y);
//绘制圆(起始点X,起始点Y,半径,起始弧度,结束弧度,旋转方向)
//弧度=角度*Math.PI/180
oCir.arc(x, y, r, 6 * i * Math.PI / 180, 6 * (i + 1) * Math.PI / 180, false);
}
//创建从当前点回到起始点的路径
oCir.closePath();
oCir.stroke();
//画一个白色的圆去覆盖过长的分刻度
oCir.fillStyle = 'white';
oCir.beginPath();
oCir.moveTo(x, y);
oCir.arc(x, y, r * 19 / 20, 0, 360 * (i + 1) * Math.PI / 180, false);
oCir.closePath();
oCir.fill();
//绘制时的刻度
oCir.lineWidth = 3;
oCir.beginPath();
for (var i = 0; i < 12; i++) {
oCir.moveTo(x, y);
oCir.arc(x, y, r, 30 * i * Math.PI / 180, 30 * (i + 1) * Math.PI / 180, false);
}
oCir.closePath();
oCir.stroke();
//画一个白色的圆去覆盖过长的时刻度
oCir.fillStyle = 'white';
oCir.beginPath();
oCir.moveTo(x, y);
oCir.arc(x, y, r * 18 / 20, 0, 360 * (i + 1) * Math.PI / 180, false);
oCir.closePath();
oCir.fill();
//画时针
oCir.lineWidth = 5;
oCir.beginPath();
oCir.moveTo(x, y);
oCir.arc(x, y, r * 10 / 20, oHourValue, oHourValue, false);
oCir.closePath();
oCir.stroke();
//画分针
oCir.lineWidth = 3;
oCir.beginPath();
oCir.moveTo(x, y);
oCir.arc(x, y, r * 14 / 20, oMinValue, oMinValue, false);
oCir.closePath();
oCir.stroke();
//画秒针
oCir.lineWidth = 1;
oCir.beginPath();
oCir.moveTo(x, y);
oCir.arc(x, y, r * 19 / 20, oSecValue, oSecValue, false);
oCir.closePath();
oCir.stroke();
}
//一秒走一次
setInterval(toDraw,1000);
};
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# 3.4 方块旋转放大
window.onload = function () {
var oCan = document.getElementById('can');
var oR = oCan.getContext('2d');
var rotateNum = 0;
var scaleNum = 0;
var value = 0;
setInterval(toRotate, 30);
function toRotate() {
rotateNum++;
if (scaleNum == 100) {
value = -1;
} else if (scaleNum == 0) {
value = 1;
}
scaleNum += value;
oR.clearRect(0, 0, oCan.width, oCan.height);
//保存当前环境的状态
oR.save();
//重新映射画布上的 (0,0) 位置
oR.translate(200, 200);
//弧度 = 角度*Math.PI/180
oR.rotate(rotateNum * Math.PI / 100);
//fillRect应该写在rotate后面,不然rotate不起作用
oR.scale(scaleNum / 50, scaleNum / 50);
//让旋转点从左上角改成中心
//这句话应该写在scale后面,不然scale会以左上角为基点放大缩小
oR.translate(-50, -50);
oR.fillRect(0, 0, 100, 100);
oR.restore(); //返回之前保存过的路径状态和属性
}
};
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
# 四、性能优化
https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas
# 4.1 在离屏canvas上预渲染相似的图形或重复的对象
如果你发现你的在每一帧里有好多复杂的画图运算,请考虑创建一个离屏canvas,将图像在这个画布上画一次(或者每当图像改变的时候画一次),然后在每帧上画出视线以外的这个画布。
建立两个 canvas 标签,大小一致,一个正常显示,一个隐藏(缓存用的,不插入dom中),先将结果draw缓存用的canvas上下文中,因为游离canvas不会造成ui的渲染,所以它不会展现出来,再把缓存的内容整个裁剪再 draw 到正常显示用的 canvas 上,这样能优化不少。
# 4.2 避免浮点数的坐标点,用整数取而代之
当你画一个没有整数坐标点的对象时会发生子像素渲染。
浏览器为了达到抗锯齿的效果会做额外的运算。为了避免这种情况,请保证在你调用drawImage()
函数时,用Math.floor()
函数对所有的坐标点取整。
# 4.3 不要在用drawImage时缩放图像
应该在离屏canvas中缓存图片的不同尺寸。
# 4.4 使用多层画布去画一个复杂的场景
你可能会发现,你有些元素不断地改变或者移动,而其它的元素,例如外观,永远不变。这种情况的一种优化是去用多个画布元素去创建不同层次。
例如,你可以在最顶层创建一个外观层,而且仅仅在用户输入的时候被画出。你可以创建一个游戏层,在上面会有不断更新的元素和一个背景层,给那些较少更新的元素。
# 4.5 用CSS设置大的背景图
如果像大多数游戏那样,你有一张静态的背景图,用一个静态的`` (opens new window)元素,结合background
(opens new window) 特性,以及将它置于画布元素之后。这么做可以避免在每一帧在画布上绘制大图。
# 4.6 用CSS transforms特性缩放画布
CSS transforms (opens new window) 特性由于调用GPU,因此更快捷。最好的情况是,不要将小画布放大,而是去将大画布缩小。
# 4.7 关闭透明度
如果你的游戏使用画布而且不需要透明,当使用 HTMLCanvasElement.getContext()
创建一个绘图上下文时把 alpha
选项设置为 false
。这个选项可以帮助浏览器进行内部优化。
let ctx = canvas.getContext('2d', { alpha: false });
# 4.8 避免不必要的画布状态改变
有动画,请使用window.requestAnimationFrame()
(opens new window) 而非window.setInterval()