canvas 可以用于动画、游戏画面、数据可视化、图片编辑以及实时视频处理等内容。
canvas api 主要聚焦于 2D 图形。
WebGL api 主要聚焦于硬件加速的 2D 和 3D 图形。
先来看一个基础事例:
<canvas id="canvas">
Your browser not support canvas, please update browser.
</canvas>
<script>
function draw() {
// 获取 canvas 元素
const canvas = document.getElementById('canvas');
// 创建画板
const context = canvas.getContext('2d');
// 填充形状
context.fillRect(10, 10, 55, 50);
// 填充颜色
context.fillStyle = 'black';
}
draw();
</script>
这时,浏览器会出现一个黑色的方块:
绘制图形
首先学习如何绘制矩形。
绘制矩形
canvas 提供了 3 种方法来绘制矩形:
- fillRect(x, y, width, height): 绘制了一个填充的矩形;
- strokeRect(x, y, width, height): 绘制了一个矩形的边框;
- clearRect(x, y, width, height): 清楚指定矩形区域,让清楚部分完全透明;
- rect(x, y, width, height): 矩形路径
使用以上三个方法构建一个空心矩形:
function draw() {
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
context.fillRect(25, 25, 100, 100);
context.clearRect(50, 50, 50, 50);
}
draw();
如图下所示:
绘制路径
绘图的基本元素是路径。
路径:通过不同颜色和宽度的线段或曲线相连形成的不同形状的点的集合
一下是所需要的函数:
- beginPath(): 新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径;
- closePath(): 闭合路径之后图形绘制命令又重新指向到上下文中;
- stroke(): 通过线条来绘制图形轮廓;
- fill(): 通过填充路径的内容区域生成实心的图形了;
用以上的方法来构建一个矩形:
function draw() {
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
context.beginPath();
context.moveTo(100, 100);
context.lineTo(100, 0);
context.lineTo(0, 0);
context.lineTo(0, 100);
context.fill();
}
draw();
生成路径:
-
beginPath(): 本质上,路径是由很多子路径构成,这些子路径都是在一个列表中,所有的子路径来构成图形。每次调用这个方法,列表都会清空,然后就可以重新绘制新的图形了;
-
moveTo(): 设置路径之后,指定起始位置;
-
lineTo(): 调用函数绘制路径;
-
closePath(): 闭合路径(非必需),在调用 fill() 函数时,所有没有闭合的形状都会自动闭合。
移动笔
moveTo(): 从画布上的一个点移动到另外一个点。
绘制直线
使用 lineTo(x, y) 方法:从当前位置到 (x, y) 的指导位置的线段。
绘制圆弧
- arc(x, y, radius, startAngle, endAngle, anticlockwise): 画一个以 (x, y) 为圆心的以 radius 为半径的圆弧/园。从 startAngle 开始,到 endAngle 结束,按照 anticlockwise 给定的方向。
- arcTo(x1, y1, x2, y2, radius): 根据给定的控制点和半径画一段圆弧,再以直线链接两个控制点。
arc()
函数中,表示角度的单位是弧度,不是角度。角度转弧度的公式为:弧度 = 角度 * (π/ 180)
使用以上方法,画一个实心圆:
function draw() {
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
context.beginPath();
context.moveTo(50, 50);
context.arc(50, 50, 25, 0, 2 * Math.PI, true);
context.fill();
}
draw();
如图下所示:
Path2D
为了简化代码以及提高性能,我们可以使用 Path2D 用来缓存或者记录绘画命令。
来实验一下,来画一个内切圆:
function draw() {
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
const rectangle = new Path2D();
rectangle.rect(0, 0, 100, 100);
const circle = new Path2D();
circle.moveTo(50, 50);
circle.arc(50, 50, 50, 2 * Math.PI, false);
context.stroke(rectangle);
context.stroke(circle);
}
draw();
SVG Path
可以使用 svg path data 来初始化 canvas 上面的路径
到现在为止,我们可以使用一系列路径来将我们所需要的图形,绘画在画布上面了。大体上的步骤如下:
- 创建画布
- 开始路径
- 设置初始点
- 绘画路径
- 闭合路径
示例
接下来,就通过上面学习到的知识来画一个简单的柱状图:
轴线
生成轴线:
// 坐标轴生成
createAxle(type = 'x') {
const {point, axleLength} = this.defaults;
const axle = new Path2D();
axle.moveTo(...point);
if (type === 'x') {
axle.lineTo(point[0] + axleLength, point[1]);
} else {
axle.lineTo(point[0], point[1] - axleLength);
}
this.context.stroke(axle);
}
刻度
生成刻度:
// 生成刻度
createScale(type = 'x') {
const {point, count, axleLength, scaleLength} = this.defaults;
const ruleWidth = parseInt(axleLength / count, 10);
const scale = new Path2D();
for (let i = 0; i <= axleLength; i += ruleWidth) {
if (type === 'x') {
scale.moveTo(point[0] + i, point[1]);
scale.lineTo(point[0] + i, point[1] + scaleLength);
} else {
scale.moveTo(point[0], point[1] - i);
scale.lineTo(point[0] - scaleLength, point[1] - i);
}
}
this.context.stroke(scale);
}
画出柱状图
最后一步,我们就要画出柱状图的关键,柱子了。
// 画柱状图
createRect() {
const {point, axleLength, count} = this.defaults;
const rectLength = parseInt(axleLength / count, 10);
const rect = new Path2D();
for (let i = 0; i <= count; i += 1) {
rect.rect(
point[0] + (rectLength * i),
point[1],
rectLength,
-rectLength * i
);
}
this.context.fill(rect);
}
完整代码
class LineChart {
constructor(config) {
const canvas = document.getElementById('canvas');
this.context = canvas.getContext('2d');
this.defaults = {
point: [50, 120],
axleLength: 100,
count: 5,
scaleLength: 5
};
Object.assign(this.defaults, config);
}
// 坐标轴生成
createAxle(type = 'x') {
const {point, axleLength} = this.defaults;
const axle = new Path2D();
axle.moveTo(...point);
if (type === 'x') {
axle.lineTo(point[0] + axleLength, point[1]);
} else {
axle.lineTo(point[0], point[1] - axleLength);
}
this.context.stroke(axle);
}
// 坐标标尺
createScale(type = 'x') {
const {point, count, axleLength, scaleLength} = this.defaults;
const ruleWidth = parseInt(axleLength / count, 10);
const scale = new Path2D();
for (let i = 0; i <= axleLength; i += ruleWidth) {
if (type === 'x') {
scale.moveTo(point[0] + i, point[1]);
scale.lineTo(point[0] + i, point[1] + scaleLength);
} else {
scale.moveTo(point[0], point[1] - i);
scale.lineTo(point[0] - scaleLength, point[1] - i);
}
}
this.context.stroke(scale);
}
// 画柱状图
createRect() {
const {point, axleLength, count} = this.defaults;
const rectLength = parseInt(axleLength / count, 10);
const rect = new Path2D();
for (let i = 0; i <= count; i += 1) {
rect.rect(
point[0] + (rectLength * i),
point[1],
rectLength,
-rectLength * i
);
}
this.context.fill(rect);
}
draw() {
this.createAxle('x');
this.createAxle('y');
this.createScale('x');
this.createScale('y');
this.createRect();
}
}
const lineChart = new LineChart();
lineChart.draw();
结果如下所示:
剩下的工作还有一些,之后在更新吧!
- 美化这个柱状图啊,现在这个太黑了
- 标上刻度
- 根据数据来对柱子进行调节
PS:大家看了后觉得对自己有帮助可以三连留言,欢迎提出宝贵意见,也欢迎各位对相关技术有兴趣的开发者(由团队开发人员微信号x118422邀请)入群,在线解答讨论数据可视化、优化图表性能等方面的技术问题~