Canvas 入门指南

808 阅读4分钟

mdn 文档

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>

这时,浏览器会出现一个黑色的方块:

CleanShot 2021-05-17 at 10.55.23@2x.png

绘制图形

首先学习如何绘制矩形。

绘制矩形

canvas 提供了 3 种方法来绘制矩形:

  1. fillRect(x, y, width, height): 绘制了一个填充的矩形;
  2. strokeRect(x, y, width, height): 绘制了一个矩形的边框;
  3. clearRect(x, y, width, height): 清楚指定矩形区域,让清楚部分完全透明;
  4. 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();

如图下所示:

CleanShot 2021-05-17 at 11.21.35@2x.png

绘制路径

绘图的基本元素是路径。

路径:通过不同颜色和宽度的线段或曲线相连形成的不同形状的点的集合

一下是所需要的函数:

  1. beginPath(): 新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径;
  2. closePath(): 闭合路径之后图形绘制命令又重新指向到上下文中;
  3. stroke(): 通过线条来绘制图形轮廓;
  4. 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();

生成路径:

  1. beginPath(): 本质上,路径是由很多子路径构成,这些子路径都是在一个列表中,所有的子路径来构成图形。每次调用这个方法,列表都会清空,然后就可以重新绘制新的图形了;

  2. moveTo(): 设置路径之后,指定起始位置;

  3. lineTo(): 调用函数绘制路径;

  4. closePath(): 闭合路径(非必需),在调用 fill() 函数时,所有没有闭合的形状都会自动闭合。

移动笔

moveTo(): 从画布上的一个点移动到另外一个点。

绘制直线

使用 lineTo(x, y) 方法:从当前位置到 (x, y) 的指导位置的线段。

绘制圆弧

  1. arc(x, y, radius, startAngle, endAngle, anticlockwise): 画一个以 (x, y) 为圆心的以 radius 为半径的圆弧/园。从 startAngle 开始,到 endAngle 结束,按照 anticlockwise 给定的方向。
  2. 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();

如图下所示:

CleanShot 2021-05-17 at 11.50.08@2x.png

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();

CleanShot 2021-05-17 at 12.09.55@2x.png

SVG Path

可以使用 svg path data 来初始化 canvas 上面的路径

到现在为止,我们可以使用一系列路径来将我们所需要的图形,绘画在画布上面了。大体上的步骤如下:

  1. 创建画布
  2. 开始路径
  3. 设置初始点
  4. 绘画路径
  5. 闭合路径

示例

接下来,就通过上面学习到的知识来画一个简单的柱状图:

轴线

生成轴线:

// 坐标轴生成
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();

结果如下所示:

CleanShot 2021-05-17 at 14.42.14@2x.png

剩下的工作还有一些,之后在更新吧!

  • 美化这个柱状图啊,现在这个太黑了
  • 标上刻度
  • 根据数据来对柱子进行调节

PS:大家看了后觉得对自己有帮助可以三连留言,欢迎提出宝贵意见,也欢迎各位对相关技术有兴趣的开发者(由团队开发人员微信号x118422邀请)入群,在线解答讨论数据可视化、优化图表性能等方面的技术问题~