canvas 性能优化篇
1.3k5WEBcanvas2019-03-18

看了很多大佬的文章,自己也对项目做了一些优化,其实有很多地方平常码代码的时候稍微注意一下就能节约很多性能开销

1.离屏渲染

在离屏canvas上预渲染相似的图形或重复的对象,通俗的解释是将离屏canvas当成预渲染,在离屏canvas上绘制好一整块图形,绘制好后在放到视图canvas中,适合每一帧画图运算复杂的图形

比如你想把一张图片放到canvas上,使用drawImage()方法,有三种写法

1
2
3
4
5
6
// 将image放到目标canvas指定位置
void ctx.drawImage(image, dx, dy);
// 将image放到目标canvas指定位置,指定宽高渲染
void ctx.drawImage(image, dx, dy, dWidth, dHeight);
// 将image裁剪之后放到目标canvas指定位置,指定宽高渲染
void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

第一种只是把图片原样放到canvas里,第二章指定宽高就意味着放大或者缩小图片后再放进去,带三种是裁剪后再放大或者缩小放到canvas中,这三种写法操作依次增加,性能开销也是依次提高
而离屏渲染就可以让我们先把图片裁剪成想要的尺寸内容保存起来,绘制的时候就可以使用第一种写法简单的把图片放进去就完了

1
2
3
4
5
6
7
8
9
// 在离屏 canvas 上绘制
var offscreencanvas = document.createElement('canvas');
// 宽高赋值为想要的图片尺寸
offscreencanvas.width = dWidth;
offscreencanvas.height = dHeight;
// 裁剪
offscreencanvas.getContext('2d').drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
// 在视图canvas中绘制
viewcontext.drawImage(canvas, x, y);

2.分层画布

多个相互重叠的canvas根据变化程度分开渲染,越复杂的场景越适合

比如一个简单的游戏场景,游戏背景始终不变或者变化次数较少但是人物游戏的主体是一直在根据玩家的指挥不停的改变,

场景:最近写的一个画板,比如这样一个简单的画圆,可以看到有原来的绘画的痕迹,那就要 渲染没画圆之前的画布 -> 再画圆,鼠标移动的时候不断的执行这个渲染过程

示例画圆gif

这个时候就可以使用分层画布,画画在一个上层canvas上,原来绘画痕迹在下层canvas上,那么画圆的过程就是清空 -> 画圆,画好之后再放到下层canvas上,这样就不需要去渲染之前的画布,就能节约性能开销

3.一次性绘制

绘制操作的性能开销较高,可以创建一个包含所有线条的路径,然后通过单个绘制路径调用进行绘制

从这样

1
2
3
4
5
6
7
8
for (var i = 0; i < points.length - 1; i++) {
var p1 = points[i];
var p2 = points[i+1];
context.beginPath();
context.moveTo(p1.x, p1.y);
context.lineTo(p2.x, p2.y);
context.stroke();
}

变成这样,在绘制复杂路径时,最好将所有点都放入路径中,而不是分别呈现各个片段

1
2
3
4
5
6
7
8
context.beginPath();
for (var i = 0; i < points.length - 1; i++) {
var p1 = points[i];
var p2 = points[i+1];
context.moveTo(p1.x, p1.y);
context.lineTo(p2.x, p2.y);
}
context.stroke();

4.使用requestAnimationFrame执行动画

canvas动画的本质是不断地擦除和重绘,再结合一些时间控制的方法达到动画的目的
显示器刷新频率是60Hz,最平滑动画的最佳循环间隔是1000ms/60,约等于16.6ms
而requestAnimationFrame就是根据显示器刷新频率来的,这是浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,节省系统资源,提高系统性能,如果页面不是激活状态下的话,requestAnimationFrame() 会被暂停调用以提升性能和电池寿命

详细看这儿

5.清空画布

三种方法性能,性能依次提高

1
2
3
context.fillRect()
context.clearRect()
canvas.width = canvas.width; // 一种画布专用的技巧

6.减少调用canvas的api

比如像背景可以使用css属性设置或者img标签加一些定位什么的

画布的缩放可以使用CSS transforms,不要将小画布放大,而是去将大画布缩小

1
2
3
4
5
6
7
8
var scaleX = canvas.width / window.innerWidth;
var scaleY = canvas.height / window.innerHeight;

var scaleToFit = Math.min(scaleX, scaleY);
var scaleToCover = Math.max(scaleX, scaleY);

stage.style.transformOrigin = '0 0'; //scale from top left
stage.style.transform = 'scale(' + scaleToFit + ')';

其他注意点

  • 尽可能使用计算代替canvas渲染,通常情况下,渲染比计算的开销大很多(3~4 个量级)
  • 减少改变 context 的状态以及不要赋一些乱七八糟类型的值,比如人家要一个number你要给一个string,浏览器会用一些额外时间来处理这些非法输入,可能会造成三四倍的时间开销
  • 避免使用浮点数坐标,使用非整数的坐标绘制内容,系统会自动使用抗锯齿功能,尝试对线条进行平滑处理,这又是一种性能消耗。可以调用 Math.round 四舍五入取整
  • 减少使用 shadowBlur 效果,和很多图像环境渲染一样,阴影渲染的性能开销通常比较高

参考文章

https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas
https://www.html5rocks.com/zh/tutorials/canvas/performance/
http://taobaofed.org/blog/2016/02/22/canvas-performance/