什么是canvas

canvas是指通过使用javascript api在网上绘制图形的一种技术,主要打破了以往在web中只能通过image插入图片的局限。与其同类的竞品有svg。

关注点

无论是svg还是canvas,web中2D图形绘制的主要关注点包括但不限于:

  • 骨骼(可类比web中的html)
    • 绘制图形
    • 绘制文字
  • 皮肤(可类比web中的css):给图形和文字添加样式
  • 坐标系以及坐标系的转换
  • 动作(可类比web中的JS):动画
  • 处理图片
  • 事件监听和响应

使用方法

<canvas id="tutorial" width="150" height="150"></canvas>

var canvas = document.getElementById('tutorial');
var ctx = canvas.getContext('2d');

通过选择器获取一个指定宽高的canvas元素,得到该canvas元素的上下文。这个上下文包含了所有绘图的方法和属性。

绘制图形

canvas上下文只支持绘制两种基本图形:rect和path。

绘制rect

绘制矩形主要通过fillRect(x, y, width, height)

基于path绘制其他图形

像三角形,圆,正方形等其他图形都是通过一条条path(直线或曲线)拼接而成(乐此不疲的程序员们又可以开始愉快地封装和造轮子了)。

首先你得有一块画布,手里握着一只笔,每画一个通过多条path拼接而成的图形,需要走下面的流程:

  • ctx.beginPath() // 本宝宝要开始表演了
  • ctx.moveTo() // 把笔放到画布上的某一个点
  • ctx.lineTo() || ctx.arc() || ctx.quadraticCurveTo() || ctx.bezierCurveTo() // 画直线||画弧线||画二阶贝塞尔曲线||画三阶贝塞尔曲线
  • ctx.stroke() || ctx.fill // 只画轮廓||填充这个图形

所以,你就像一个抠脚画家一样,在画布上不断地画图形。

绘制文字

说到文字,我印象最深的就是word中的艺术字了。回归正题,canvas中绘制文字很简单:fillText()

给图形和文件添加样式

在css中是通过css属性增加样式,在svg中可以通过指定部分css通用属性以及svg元素专有的属性添加样式;而在canvas这个以javascript api为模式设计的世界里,添加样式同样是通过调用函数或属性复制的方式,在绘制图形之前你可以先指定样式:

ctx.strokeStyle = '#09f';
ctx.lineWidth   = 2;
ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';

之后画出来的图形都会拥有以上样式。给文字添加样式和给图形添加样式的方法大同小异。

值得一提的是颜色渐变和平铺模式这两个样式。

坐标系以及坐标系的转换

坐标系

在canvas中坐标轴的原点在左上方,x轴从左到右,y轴则自顶向下。

坐标系的转换

熟悉css3的同学应该都知道transform这个属性,用于平移,旋转,放大缩小某个dom元素,可以指定一个变换矩阵来实现更复杂的变换(线代中的矩阵变换)。知识点都是相通的,svg和canvas也是通过坐标系的变换来实现图形变换,只是具体场景有具体的最佳实践。

svg中的坐标系变换

在svg中是通过将一组图形放到一个g(group的缩写)元素下面,然后对这个g元素添加transform属性。假如这个g元素指定了相对x轴向右平移20px,那么g元素就获得了一个新的坐标系,其内部的所有元素都参考这个新的坐标系。

canvas中的坐标系变换

canvas将状态(坐标系样式)存储为一个栈的数据结构。调用ctx.save将当前状态推入stack,调用ctx.restore则执行let top=stack.pop(),并将当前状态置为top状态。由于后一个绘制的图形的坐标系可能与当前绘制的元素的坐标系无关,所以在改变当前元素坐标系前要先save,绘制完当前元素后restore,以便后一个元素能正常绘制,这种实现方式理解起来其实是有点反人类的。

举个例子,假设你想通过变换坐标系的方法通过循环画一排3个矩形。

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  for (var i = 0; i < 3; i++) {
      ctx.save(); //保存相对于原点的坐标系
      ctx.translate(j * 50,0);
      ctx.fillRect(0, 0, 25, 25);
      ctx.restore(); //恢复相对于原点的坐标系
    }
  }
}

动画

canvas动画其实就是基于JS实现的动画:

  • 调用clearRect()清屏
  • 绘制当前帧的图像
  • 通过requestAniamtion,setInterval,setTimeout启动下一帧的绘制。 从这里其实可以看出:当画布中绘制了很多图形,如果只是因为其中某一个图形需要动,其他图形就不得不跟着重新绘制一遍。

处理图片

图形和图片的关系并不是割裂的,canvas的出现绝非是为了替代img,相反两者结合起来还可以实现相册,头像框之类的功能。

导入图片

可以通过ctx.drawImage(img)往画布中导入一张图片

处理图片

canvas的一大特性是支持像素级别的操作。通过该ctx.getImageData可以获得一个数组,数组的每四项分别代表一个像素的rgba。通过修改每个像素即可实现所谓的PS,比如设置每个元素的R,G,B分别为其对应RGB的和的平均值即可实现灰度图。

导出图片

canvas的另一大特性则是支持图片导出,

事件监听和响应

作为web中的一个重要技术,其应用场景不可避免地会涉及到交互。比如图表库(echartjs),h5游戏。不幸的是,相比于svg天然就可以享受到dom元素的事件监听机制,canvas对事件监听的支持目前只有三个实验性质的API。个人斗胆猜测echart在实现交互中一定实现了不少数学方法,比如判断用户是否点击了某个圆,就求一下鼠标和圆心的距离是否小于半径。百度在技术选型方面(不选择svg)也许有自己的想法,但我相信这种功能在不久的将来,会像当年在css中用图片实现图片圆角一样,被一个border-raidus完美取代。

参考