• Home
  • About
    • Damon photo

      Damon

      Make a daydream, with me.

    • Learn More
    • Twitter
    • Facebook
    • Instagram
    • Github
  • Posts
    • All Posts
    • All Tags

canvas性能优化

20 Mar 2019

Reading time ~1 minute

canvas性能优化

最近由于使用到canvas来制作画笔的功能,开始研究起canvas的优化了。查阅了很多文章,大多数都是12年左右的了。canvas本身的api也不算多,可优化的可能性也被例举的很充分了。这里只是做了一些学习和整理工作。

canvas主要有两个操作,一个是计算,一个是绘制(render)。而绘制的量级要远大于计算。所以我们要尽可能的减少渲染的次数。

离屏canvas渲染

我们可以把需要绘制的内容放在一个离屏的canvas中(就是不可见的canvas,浏览器并没有渲染它,需要和OffscreenCanvas的概念区分开来)。然后在需要渲染的时候渲染出来。比如说我们fps60的情况下,每一帧可能做了很多绘制,这一帧的绘制都可以在离屏canvas中绘制,然后在一帧的最后把canvas渲染过来(drawImage可以绘制canvas)。这样可以大大减少渲染的开销。

另外,重复的东西也可以预先绘制在一个离屏的canvas中,然后再把整个canvas渲染出来。

这两种情况下,把canvas的大小压缩到最小区域,也有助于我们减少渲染的开销。

减少绘制调用

尽量减少绘制方法的调用,压缩次数。比如说polylines(多段线)可以在最后统一绘制

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

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

下面这种方法可以获得更好的性能,先把路径计算好,在最后统一绘制比分开分段绘制的性能要好。

减少状态机的改变

频繁更改状态是昂贵的,减少状态机的改变会提升渲染的性能。比如在我们需要绘制条纹属性的东西时,我们可以频繁更改填充的颜色,然后绘制,或者我们可以把一种颜色绘制完成

for (var i = 0; i < STRIPES; i++) {
  context.fillStyle = (i % 2 ? COLOR1 : COLOR2);
  context.fillRect(i * GAP, 0, GAP, 480);
}

context.fillStyle = COLOR1;
for (var i = 0; i < STRIPES/2; i++) {
  context.fillRect((i*2) * GAP, 0, GAP, 480);
}
context.fillStyle = COLOR2;
for (var i = 0; i < STRIPES/2; i++) {
  context.fillRect((i*2+1) * GAP, 0, GAP, 480);
}

下面这种方法,虽然代码量增加了,但是状态的改变次数减少了。这也是非常实用的一个提升canvas性能的方法。

避免使用浮点数坐标进行drawImage的绘制

我觉得这个技巧非常实用和神奇,就是在绘制图案时使用整数。使用浮点数时,浏览器会自动去做额外的计算来做抗锯齿,并且会导致你的图像非常模糊。

使用多个canvas(多层)

如果可以把一个场景拆分,把不需要重新渲染的部分,比如背景放在单独的层里,把需要频繁渲染和有关联的图像放在一个canvas上,这样可以减少渲染的消耗,尽量渲染小部分的东西而不是整块都重新渲染。

仅渲染有更改的部分

尽量只渲染更改的部分,记录修改的bounds,只重新绘制该bounds的部分。例如

context.fillRect(last.x, last.y, last.width, last.height);

了解清楚图案的多种方法

除了最常用的clearRect方法,其实对canvas的大小做更改的时候也会清空内容,甚至大小没有更改,只是

canvas.width = canvas.width

就会清空画布啦。另外fillRect也可以使用。了解这几种清楚图像的方法可以避免一些重复操作。

OffscreenCanvas

新标准里引入了OffscreenCanvas,目前比较少的浏览器支持。OffscreenCanvas和之前的离屏canvas不是一个概念,这个OffscreenCanvas主要可以配合Web Worker使用。

这里就不赘述Web Worker的使用了。

Web worker不可以对dom做操作,也不可以使用dom的api。但是有了这个OffscreenCanvas就可以在web worker中操作canvas啦。

主要就是不会阻塞主线程的运行,所以我觉得最好的使用是在计算一些比较复杂的算法和比较耗时的操作上,比如getImageData这类消耗比较大,并且要对获取到的数据做一些算法,可以放在web worker中做,然后再回传给主线程。

当然在web worker的OffscreenCanvas中也可以做渲染的工作并利用imagebitmap回传给主线程。也可以在web worker中使用requestAnimationFrame方法。

可以参考MDN中给出了具体的使用方法

https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas

不过要使用这个还有很长一顿时间,chrome69以后才支持,这又是一条慢慢长路需要等待呀。

References:

Improved Performance with OffscreenCanvas https://newinweb.com/2018/09/10/offscreen-canvas/

MDN Offscreen​Canvas https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas

MDN OffscreenCanvas — Speed up Your Canvas Operations with a Web Workerhttps://developers.google.com/web/updates/2018/08/offscreen-canvas

Boris Smus Improving HTML5 Canvas Performance https://www.html5rocks.com/en/tutorials/canvas/performance/

MDN Optimizing canvas https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas



学习 Share Tweet +1