了解下canvas?

luvsic  |  2019. 06. 21   |  阅读 361 次
frontend

Canvas API 提供了通过 JavaScript 绘制图形的能力 。它广泛用于动画、游戏图形、数据可视化、照片处理和实时视频处理领域。本文将会简单介绍下 canvas 。文章的大部分内容来源于 MDN 的 canvas 教程,想深入了解 canvas 的可以去看一下。

初识 canvas

// 栗子 1
<canvas id="tutorial" width="150" height="150"></canvas>  

这就是一个 canvas 标签,看起来和普通的 html 标签没什么不同,重要的通过 canvas 标签我们可以获取其渲染上下文,canvas 的所有API都通过这个渲染上下文暴露出来,以下讲解的 API 也都基于此上下文。

canvas 翻译过来是画布的意思,所以接下来我们会对照画布来介绍 canvas 的API。

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

绘制图形

image.png

canvas 坐标系统中,元素的左上角为坐标原点,向右下延伸坐标轴,所有图形相对于原点绘制,默认 1 网格单位对应 1 像素,比如栗子1 中的 canvas 元素就形成一个 150X150 的坐标系。 上图中的矩形坐标即为 (x, y)。
和 svg 不同,canvas 只提供了一种基本图形:矩形,不过借助路径我们可以绘制想要的任意图形。

绘制矩形

canvas 提供了三种矩形 API,我们可以通过渲染上下文获取到它们。所有 API 接受四个参数: x, y, width, height。

function draw() {  
  var canvas = document.getElementById('canvas');
  if (canvas.getContext) {
    var ctx = canvas.getContext('2d');

    ctx.fillRect(25, 25, 100, 100);
    ctx.clearRect(45, 45, 60, 60);
    ctx.strokeRect(50, 50, 50, 50);
  }
}

 fillRect 和 strokeRect 分别会 “填充” 和 “描边“ 一片矩形区域。调用它们会立即绘制到画布上。
clearRect 会像橡皮擦一样清除特定的矩形区域。
结果如图:

image.png

绘制路径

路径由一系列的点组成,通过路径的组合我们可以绘制需要的任何图形。绘制一条路径步骤如下:

  1. 通过 beginPath  创建路径,并创建一个路径缓存区,当前坐标为起点。
  2. 然后通过 绘制命令 绘制路径,将绘制的 直线、弧线等存入缓存区,从而组合成图形。
  3. 最后 closePath 会在当前坐标和起点连起一条直线(可选), 最后可以 填充 或 描边 路径。

这个绘制步骤和画画差不多,落笔、描边到填充和真实世界的心智模型是相同的。其中的重头戏就是步骤2的绘制命令:
lineTo 、 arc 、[bezierCurveTo()](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/bezierCurveTo)等命令分别绘制直线、弧线和贝塞尔曲线路径。而 moveTo  命令接受 x、y 两个参数,调用其会将画笔移动到指定的 x,y 坐标。
比如这里有个稍微复杂的栗子,绘制一个吃豆人

路径复用

在吃豆人的栗子里,我们反复调用 roundedRect 函数来生成圆角矩形, canvas 提供了路径复用的方法,这就是 Path2D,通过 Path2d 我们可以创建路径对象,调用绘制命令、甚至合并两个路径对象。以吃豆人中的圆角矩形函数为栗,使用Path2D 后的代码:

// A utility function to draw a rectangle with rounded corners.
function roundedRect(ctx, x, y, width, height, radius) {  
  const path = new Path2D();
  path.moveTo(x, y + radius);
  path.lineTo(x, y + height - radius);
  path.arcTo(x, y + height, x + radius, y + height, radius);
  path.lineTo(x + width - radius, y + height);
  path.arcTo(x + width, y + height, x + width, y + height-radius, radius);
  path.lineTo(x + width, y + radius);
  path.arcTo(x + width, y, x + width - radius, y, radius);
  path.lineTo(x + radius, y);
  path.arcTo(x, y, x, y + radius, radius);

  ctx.stroke(path);
}

加点样式?

canvas 通过状态机来保持 画笔 和 画布的状态,我们可以通过 API 来改变画笔的状态来绘制不同样式的图形。比如通过 fillStyle 和 strokeStyle 改变填充和描边的样式,通过 lineWidth 改变画笔的尺寸等。通过 createLinearGradient 、[createPattern(image, type)](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/createPattern)来绘制渐变色和图片背景,这里不再赘述。

文本绘制

去看原文吧

坐标轴变换

去看原文吧

加点动画?

canvas 动画的限制在于:图形绘制结束后无法再更改图形的状态(尺寸、颜色、坐标等)。这和我们熟悉的 html 元素不一样,dom 元素在渲染后还可以进行做位移、缩放等动画。由于以上原因,在 canvas 中做动画需要遵循以下步骤:

  1. 使用 clearRect 等方法清除画布
  2. 保存 canvas 的初始状态
  3. 绘制当前动画帧,这一步你可能会修改 canvas 状态
  4. 恢复 canvas 初始状态。

这里有一个时钟动画的栗子,我们将不同的图形封装为对象,绘制图形时只需要调用对象的 draw 方法,这样统一了编程模型。

class Hour {  
    constructor(ctx) {
        this.ctx = ctx;
    }
    draw() {
        this.ctx.lineWidth = 14;
        this.ctx.beginPath();
        this.ctx.moveTo(-20, 0);
        this.ctx.lineTo(80, 0);
        this.ctx.stroke();
    }
}

绘制的每一帧我们会不断更新画布的状态,为了不污染下一帧的画布状态,我们需要不断调用 save  和 restore 来保存和回退画布状态。

这一章简单介绍了 canvas的基本知识,下篇文章会涉及 canvas 内的事件处理、碰撞检测等内容,并利用这些实现一些炫酷的效果,敬请期待。

分享到

   
浅谈 react 基本合成事件类
加入我们