1、Canvas是html5新增的画布元素,允许我们使用Js来绘制图形
2、Canvas最常见的用途是渲染动画。
渲染动画的基本原理无非是反复的擦除和重绘。
为了动画的流畅,留了一帧的时间,我们需要在16ms完成游戏逻辑的处理,对象位置、状态的计算以及把他们画出来。一旦消耗时间多了,用户就会感觉卡顿。所以提高动画的性能很重要
3、优化渲染性能总体思路:
- 在每一帧中,尽可能减少调用渲染相关的API次数
- 在每一帧中,尽可能调用那些渲染开销较低的API
- 在每一帧中,尽可能以导致开销最低的方式调用渲染相关API
4、Canvas API都在其上下文对象context上调用的。
var context = drawing.getContext("2d");
①context是一个状态机。
②几乎所有的渲染操作都是在context上完成的,最终的效果与context本身的状态有关系,例如:
context.lineWidth = 5; //矩形边框宽度
context.strokeStyle = "red"; //矩形边框颜色
context.strokeRect(50,50,80,80); //绘制一个矩形
以上代码的第一二行必须放在第三行上边,要不然不起作用。
对context.lineWidth的赋值开销远远大于对一个普通对象的赋值开销。因为canvas上下文不是一个普通的对象。。当调用context.lineWidth=5时,浏览器需要立刻做一些事情,这样你下次调用诸如strokeRect等API时,画出来的线正好是5个像素宽(这也是一种优化,否则,这些事情要等到下次stroke之前做,会更加影响性能)
③context的一些其他的属性赋值开销
属性 | 开销ms |
开销(非法赋值) |
---|---|---|
font | 1000+ | 1000+ |
shadowColor | 280+ | 400+ |
fillStyle、strokeStyle | 100+ | 200+ |
textAlign、textBaseline | 60+ | 100+ |
lineWidth、lineJoin | 40+ | 100+ |
与真正的绘制操作相比,改变context状态的开销已经算是比较小的,毕竟还没有真正的开始绘制
5、分层Cancas
出发点:动画中每种元素对渲染和动画的要求是不一样的。比如游戏,人物变化的频率是很大的(因为他们通常是走来走去的),而背景变化的频率则相对较小,很明显我们需要频繁更新和绘制人物,但是对于背景,我们也许只需有绘制一次,也许只需要每隔200ms才绘制一次,决没有必要每16ms绘制一次。分层Canvas能够大大降低完全不必要的渲染性能开销。
使用:生成多个canvas实例,把他们重叠放置,每个canvas使用不同的z-index来定义堆叠的次序,最后仅在需要绘制该层的时候进行绘制
注意:堆叠在上方的canvas中的内容会覆盖住下方canvas的内容
6、绘制图像
context.drawImage();
①数据源和绘制的性能
由于我们可以把图片的一部分绘制在canvas,所以很多时候,我们会把多个游戏对象放在一张图片上,以减少请求数量。这通常称为精灵图。但是,绘制区域与源图像尺寸相仿时的开销小于绘制区域小于源图像尺寸的开销。开销差异正是裁剪这一操作
优化:离屏绘制,我们可以把待绘制的区域裁剪好,保存。
drawImage()方法的第一个参数不仅可以接受image对象,也可以接受另一个canvas对象,两者绘制的开销基本一致
//在离屏canvas上绘制
var canvasOffscreen = document.createElement("canvas");
canvasOffscreen.width = dw;
canvasOffscreen.height = dh;
canvasOffscreen.getContext("2d").drawImage(image,sx,sy,sw,sh,dx,dy,dw,dh);
//在绘制每一帧的时候,绘制这个图形
context.drawImage(canvasOffscreen,x,y);
②视野之外的绘制
有时候,canvas只是游戏世界的一个窗口,如果我们每一帧都要把游戏世界全部画出来,势必会有很多东西画到canvas外,同样调用了绘制API,但是没有任何效果。但是我们也知道判断一个对象是否在canvas会有额外的计算开销,并且增加代码的复杂度
我做了一个实验,绘制一张 320x180 的图片 10 4 次,当我每次都绘制在 Canvas 内部时,消耗了 40ms,而每次都绘制在 Canvas 外时,仅消耗了 8ms。大家可以掂量一下,考虑到计算的开销与绘制的开销相差 2~3 个数量级,我认为通过计算来过滤掉哪些画布外的对象,仍然是很有必要的。
③避免阻塞
所谓「阻塞」,可以理解为不间断运行时间超过 16ms 的 JavaScript 代码,以及「导致浏览器花费超过 16ms 时间进行处理」的 JavaScript 代码。即使在没有什么动画的页面里,阻塞也会被用户立刻察觉到:阻塞会使页面上的对象失去响应——按钮按不下去,链接点不开,甚至标签页都无法关闭了。而在包含较多 JavaScript 动画的页面里,阻塞会使动画停止一段时间,直到阻塞恢复后才继续执行。如果经常出现「小型」的阻塞(比如上述提及的这些优化没有做好,渲染一帧的时间超过 16ms),那么就会出现「丢帧」的情况,CSS3 动画( transition 与 animate )不会受 JavaScript 阻塞的影响,但不是本文讨论的重点。
需要解决的两种阻塞:
- 频繁(通常较小)的阻塞:主要原因是过高的渲染性能开销,在每一帧中做的事情太多
- 偶尔(通常较大)的阻塞:主要原因是算法复杂度、大规模的DOM操作等
对应前者的解决方案,本文前几节中的优化方案
对于后者的解决方案:使用web Worker,在另一个线程中计算(无法操作DOM);将任务拆分成多个较小的任务,插在多帧中进行(增长执行时间)
7、优化总结:
①将渲染阶段的开销转嫁到计算阶段
②少使用font属性
③分层canvas
④
- 离屏绘制:将固定的内容预先绘制在离屏canvas
- 通过计算和判断,避免无谓的绘制操作
- 使用webwoker和拆分任务的方法避免算法复杂度过高则色动画运行