# 基础使用
- 官网 (opens new window)
- canvas对象 (opens new window)
- konva库 (opens new window)
- canvas是一块
画布
,可以用js进行绘图 - 画布要用
属性设置宽高
<canvas width="600" height="600"></canvas>
1
- 操作画图用
context
上下文
// 2d
context = canvas.getContext("2d");
// 3d
context = canvas.getContext("webgl");
1
2
3
4
5
2
3
4
5
- canvas坐标系:与css盒子坐标系相同,左上为起点
- 重要:canvas的宽高不能由css决定,必须以属性的形式存在canvas元素上(可以通过js动态修改)
- css设置的只是盒子属性的宽高,并不是画布的宽高
<body>
<canvas id="canvas" height="1000" width="1000"></canvas>
<script>
const ctx = canvas.getContext('2d');
// 定义初始位置
ctx.moveTo(100, 100);
// 描述绘图路径
ctx.lineTo(200, 100);
ctx.lineTo(100, 200);
// 自动闭合路径
ctx.closePath();
// 线宽,单位:px
// 从中间向两边平均扩散,和css盒子《不一样》
ctx.lineWidth = 10;
// 绘制颜色描述:css所有颜色描述都支持
ctx.strokeStyle = 'rgba(100, 100, 100, .5)';
// 画上页面
ctx.stroke();
// 设置填充颜色,填充
ctx.fillStyle = '#3eaf7c';
ctx.fill();
</script>
</body>
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
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
<body>
<canvas id="canvas" height="1000" width="1000"></canvas>
<script>
const ctx = canvas.getContext('2d');
canvas.style.border = '2px solid #333';
const rowNum = 100;
const colNum = 100;
// 绘制横线
for (let i = 0; i < rowNum; i++) {
ctx.moveTo(0, canvas.height / rowNum * i);
ctx.lineTo(canvas.width, canvas.height / rowNum * i);
}
// 绘制竖线
for (let i = 0; i < colNum; i++) {
ctx.moveTo(canvas.width / colNum * i, 0);
ctx.lineTo(canvas.width / colNum * i, canvas.height);
}
// 画上页面
ctx.stroke();
</script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<body>
<canvas id="canvas" height="1000" width="1000"></canvas>
<script>
(function () {
const ctx = canvas.getContext('2d');
canvas.style.border = '2px solid #333';
// 制作直角坐标系
// 起点:(100, 800)
// 终点:(800, 300)
ctx.moveTo(100, 800);
ctx.lineTo(100, 300);
ctx.lineTo(90, 310);
ctx.moveTo(100, 300);
ctx.lineTo(110, 310);
ctx.moveTo(100, 800);
ctx.lineTo(800, 800);
ctx.lineTo(790, 790);
ctx.moveTo(800, 800);
ctx.lineTo(790, 810);
ctx.strokeStyle = 'black';
ctx.stroke();
})();
function draw(arr) {
const ctx = canvas.getContext('2d');
// 声明新状态,后续可以覆盖原状态
ctx.beginPath();
const len = arr.length;
const max = Math.max(...arr);
// 坐标x/y轴单位长度
const yStep = (500 / max) * 0.8;
const xStep = Math.floor((700 / len) * 0.8);
ctx.moveTo(100 + xStep, 800 - arr[0] * yStep);
for (let i = 1; i < arr.length; i++) {
ctx.lineTo(100 + xStep * (i + 1), 800 - arr[i] * yStep);
}
ctx.strokeStyle = 'red';
ctx.stroke();
}
const data = [1, 20, 3, 12, 5, 9, 7, 26, 53, 110, 70, 33];
draw(data);
</script>
</body>
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
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
<body>
<canvas id="canvas" height="1000" width="1000"></canvas>
<script>
const ctx = canvas.getContext('2d');
// (左上角坐标 x, 左上角坐标 y, 宽, 高)
ctx.strokeRect(10, 100, 300, 100);
// ctx.rect(10, 100, 300, 100);
// ctx.stroke();
</script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
// 填充矩形
ctx.fillStyle = 'red';
ctx.fillRect(10, 100, 300, 100);
1
2
3
2
3
// 矩形橡皮擦
ctx.clearRect(10, 100, 20, 100);
1
2
2
ctx.fillStyle = 'blue';
ctx.font = '15px "微软雅黑"';
ctx.textBaseLine = 'bottom';
ctx.textAlign = 'center';
// 空心文字
ctx.strokeText('你好啊啊啊啊', 100, 100);
// 实体文字
ctx.fillText('你好', 100, 200);
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
- baseline设置
- 矩形渐变
<body> <canvas id="canvas" height="1000" width="1000"></canvas> <script> const ctx = canvas.getContext('2d'); const grd = ctx.createLinearGradient(0, 0, 300, 0); grd.addColorStop(0, 'red'); grd.addColorStop(.5, 'green'); grd.addColorStop(1, 'blue'); ctx.fillStyle = grd; ctx.fillRect(0, 0, 300, 300); </script> </body>
1
2
3
4
5
6
7
8
9
10
11
12 - 圆形渐变
<body> <canvas id="canvas" height="1000" width="1000"></canvas> <script> const ctx = canvas.getContext('2d'); const grd = ctx.createRadialGradient(300, 300, 10, 300, 300, 200); grd.addColorStop(0, 'red'); grd.addColorStop(.5, 'green'); grd.addColorStop(1, 'blue'); ctx.fillStyle = grd; ctx.fillRect(0, 0, 700, 700); </script> </body>
1
2
3
4
5
6
7
8
9
10
11
12
# 弧形
- 绘制弧形总是和圆心、弧度打交道
// x轴方向开始计算,单位以弧度表示。
// 圆心x,圆心y,圆弧的半径,圆弧的起始点,圆弧的终点,是否逆时针
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);
1
2
3
2
3
// 已知角度30度,求弧度
30 / radius === 180 / Math.PI
radius = 30 * Math.PI / 180;
1
2
3
2
3
// 已知圆心,求圆外写文字的位置公式
// 圆心:(x0, y0),半径:R,圆外 10px
const x = x0 + Math.cos(radius) * (R + 10);
const y = y0 + Math.sin(radius) * (R + 10);
1
2
3
4
2
3
4
<body>
<canvas id="canvas" height="1000" width="1000"></canvas>
<script>
const ctx = canvas.getContext('2d');
// 半圆, 2PI 为一个圆
ctx.arc(100, 100, 100, 0, 1 * Math.PI);
ctx.stroke();
ctx.fillStyle = 'red';
ctx.fill();
</script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
- 注,每次都要注意重置起始圆心位置和重置状态以变更不同颜色
<body>
<canvas id="canvas" height="1000" width="1000"></canvas>
<script>
function draw(arr) {
const ctx = canvas.getContext('2d');
const sum = arr.reduce((pre, item) => pre + item, 0);
const rate = arr.map(item => item / sum);
let start = -90 * Math.PI / 180;
let end;
for (let i = 0; i < rate.length; i++) {
// 重置状态
ctx.beginPath();
ctx.moveTo(200, 200);
end = start + 360 * rate[i] * Math.PI / 180;
ctx.arc(200, 200, 100, start, end);
const color = `#${Math.floor(Math.random() * 1000)}`
ctx.fillStyle = color;
// 计算文字应该在的位置
const x = 200 + Math.cos((2 * Math.PI * rate[i]) / 2 + start) * 110;
const y = 200 + Math.sin((2 * Math.PI * rate[i]) / 2 + start) * 110;
ctx.font = '12px "微软雅黑"';
// 让展示的文字不会和饼状图重叠
ctx.textAlign = (2 * Math.PI * rate[i] / 2 + start) > Math.PI / 2 ? 'end' : 'left';
ctx.fillText(`${(rate[i] * 100).toFixed(1)} %`, x, y);
ctx.fill();
// 重置下次绘制起始位置
start = end;
}
}
const data = [3, 12, 4, 8, 12];
draw(data);
</script>
</body>
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
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
- 将canvas画布转化成base64编码的图片
const url = canvas.toDataURL('image/jpeg', 1);
console.log(url);
1
2
2
# 分辨率问题
- 问题
- 分辨率问题
- canvas是画布,设置的宽高是一个
设备独立像素
的像素点 - 受dpr影响,一个设备独立像素可能对应多个
物理设备像素
的像素点 - 所以像素填充不饱满就会被模糊、有锯齿
- 解决
- 获取设备独立像素比例dpr:
window.devicePixelRatio
- 将画布等比放大,同时css对应的宽高不变,画布的像素就会被压缩
- 再将ctx上下文进行压缩,避免绘制计算错误:ctx.scale(dpr, dpr)
- 获取设备独立像素比例dpr:
const dpr = window.devicePixelRatio;
canvas.width = width * dpr;
canvas.height = height * dpr;
ctx.scale(dpr, dpr)
1
2
3
4
2
3
4
<body>
<canvas width="200" height="200" id="c1"></canvas>
<canvas width="200" height="200" id="c2"></canvas>
<script>
const ctx1 = c1.getContext("2d");
const ctx2 = c2.getContext("2d");
ctx1.font = '15px "微软雅黑"';
ctx1.fillText("我是画布1", 30, 30);
// 计算、压缩ctx2
const { width, height } = c2.getBoundingClientRect();
const dpr = window.devicePixelRatio;
c2.style.width = `${width}px`;
c2.style.height = `${height}px`;
c2.width = width * dpr;
c2.height = height * dpr;
ctx2.scale(dpr, dpr);
ctx2.font = '15px "微软雅黑"';
ctx2.fillText("我是画布2", 30, 30);
const c = document.createElement("canvas")
const cc = c.getContext("2d")
cc.fillText()
</script>
</body>
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
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
<body>
<script>
const canvas = document.createElement("canvas");
const dpr = window.devicePixelRatio;
const width = window.innerWidth;
const height = window.innerHeight;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
canvas.width = width * dpr;
canvas.height = height * dpr;
const ctx = canvas.getContext("2d");
ctx.scale(dpr, dpr);
ctx.fillStyle = "#333 solid"
// ctx.
ctx.fillText("你好呀", 50, 50, 100);
document.body.append(canvas);
</script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const getCtx = (canvas) => {
const canvasRect = canvas.getBoundingClientRect();
console.log(canvasRect)
const dpr = window.devicePixelRatio;
const width = canvasRect.width;
const height = canvasRect.height;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
canvas.width = width * dpr;
canvas.height = height * dpr;
const ctx = canvas.getContext("2d");
ctx.scale(dpr, dpr);
return ctx;
}
const ctx = getCtx(canvas);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 动画堆积问题
- canvas动画一般借助
requestAnimationFrame
或setTimeout
/setInterval
实现 - 浏览器收起或看不见时,会导致动画堆积,再回来容易卡顿,清除动画解决
// 离开清除烟花
window.addEventListener("visibilitychange", () => {
if (document.visibilityState === 'visible') this.fireworks = [];
})
1
2
3
4
5
2
3
4
5
# Konva
<body>
<div id="container"></div>
<script src="https://unpkg.com/konva@8/konva.min.js"></script>
<script>
// first we need to create a stage
var stage = new Konva.Stage({
container: 'container', // id of container <div>
width: 500,
height: 500
});
// then create layer
var layer = new Konva.Layer();
// create our shape
var circle = new Konva.Circle({
x: stage.width() / 2,
y: stage.height() / 2,
radius: 70,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
draggable: true
});
// add the shape to the layer
layer.add(circle);
// add the layer to the stage
stage.add(layer);
// draw the image
layer.draw();
</script>
</body>
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
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="container"></div>
<script src="./konva/index.js"></script>
<script>
class Progress {
constructor(height, width) {
this.height = height;
this.width = width;
this.schedule = 0;
this.init();
}
init() {
this.stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight
});
this.layer = new Konva.Layer();
this.out = new Konva.Rect({
x: this.stage.width() / 2,
y: this.stage.height() / 2,
width: this.width,
height: this.height,
stroke: 'blue',
strokeWidth: 4,
cornerRadius: this.height / 10,
});
this.inner = new Konva.Rect({
x: this.stage.width() / 2,
y: this.stage.height() / 2,
width: 0,
height: this.height,
fill: 'lightBlue',
cornerRadius: this.height / 10,
});
this.layer.add(this.inner);
this.layer.add(this.out);
this.stage.add(this.layer);
// draw the image
this.layer.draw();
}
pre(time) {
if (time > 100) {
this.setSchedule(100);
} else if (time < 0) {
this.setSchedule(0);
} else {
this.setSchedule(time);
}
}
setSchedule(time) {
this.inner.to({
width: this.width / 100 * time,
duration: 0.01,
easing: Konva.Easings.Linear
})
}
}
const pro = new Progress(50, 500);
let i = 0;
setInterval(() => {
if (i < 100) {
i++;
pro.pre(i);
}
}, 10);
</script>
</body>
</html>
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
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
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