彪叔 canvas高效绘制10万图形,你必须知道的高效绘制技巧

 最近的一个客户项目中,简化的需求是绘制按照行列绘制很多个圆圈。需求看起来不难,上手就可以做,写两个for循环。

原始绘制方法

首先定义了很多Circle对象,在遍历循环中调用该对象的draw方法。代码如下:

for (var i = 0; i < column; i++) {     for (var j = 0; j < row; j++) {         var circle = new Circle({             x: 8 * i + 3,             y: 8 * j + 3,             radius: 3         })         box.push(circle);     } }  console.time('time');     for (var c = 0; c < box.length; c++) {         var circle = box[c];         circle.draw(ctx);     }     console.timeEnd('time'); 

结果绘制出了按照行列排布的很多个圆圈了,如下图所示:

 

原始方法绘制很多圆圈
原始方法绘制很多圆圈

恩,很简单嘛,可以回家睡觉了。
等等,客户要求绘制的极限是10万个,而且每次绘制不能卡顿。先看下绘制10万个圆圈的时间是多久,用console.time 统计绘制时间:

console.time('time'); // 实际绘制的代码 console.timeEnd('time'); 

时间显示为几百毫秒(3到4百毫秒),如下图所示:

 

绘制时间
绘制时间

几百毫秒的绘制时间,必然是卡顿的。想要流畅操作,肯定还的优化。

批量绘制

首先想到的是批量绘制,前面的代码中,每次变量都会调用circle.draw(ctx)方法,circle.draw方法代码如下:

draw: function (ctx) {     ctx.save();     ctx.lineWidth=this.lineWidth;     ctx.strokeStyle=this.strokeStyle;     ctx.fillStyle=this.fillStyle;     ctx.beginPath();     this.createPath(ctx);     ctx.stroke();     if(this.isFill){ctx.fill();}     ctx.restore(); }, 

可以看出 每次遍历都调用了一次beginPath和stroke方法。为了提高绘制效率,我们可以只调用beginPath和stroke方法一次,把所有的子路径组织成为一个大的路径,这就是所谓的批量绘制思路,代码如下:

    console.time('time');     ctx.beginPath();     for (var c = 0; c < box.length; c++) {         var circle = box[c];         ctx.moveTo(circle.x + 3, circle.y);         circle.createPath(ctx);     }     ctx.closePath();     ctx.stroke();     console.timeEnd('time'); 

调试发现,确实效率有了很大的提升,时间减少到100毫秒左右,相当于效率提高了3-4倍左右,如下图所示:

 

批量绘制时间
批量绘制时间

需要注意的是上述代码中的moveTo语句:

ctx.moveTo(circle.x + 3, circle.y); 

这是因为: 当使用arc方法给路径中添加子路径的时候,arc所定义的路径会自动和路径集合中的最后一个路径连接起来,如下图所示:

 

arc定义的路径自动连接起来
arc定义的路径自动连接起来

此处的moveTo就是为了避免这种连接。

注意:arc 和arcTo都会有上述问题,但是rect定义的路径却不存在这种问题。

Pattern 方式

通过以上优化,客户已经觉得效率挺不错了。 但是技术研究没有止境,由于这个分布很规律,总感觉有更加快速的方法。最终突发灵感想到了一种方法,就是使用canvas 的Pattern功能:
canvas的fillStyle可以指定为一个pattern对象,而pattern可以实现一个简单图像的平铺。基于这种思路,我们可以实现如下代码:

var tempCanvas = document.createElement('canvas');  var ctx2 = tempCanvas.getContext('2d'); var w = 5,h = 5; tempCanvas.width = w; tempCanvas.height = h; dpr(tempCanvas); ctx2.fillStyle = 'red'; ctx2.arc(w/2,h/2,w/2 - 1,0,Math.PI * 2); ctx2.stroke();                    ctx.save(); ctx.beginPath(); var width = tempCanvas.width * 500,height = tempCanvas.height * 200; var pattern = ctx.createPattern(tempCanvas, 'repeat'); ctx.clearRect(100,100,width,height); ctx.rect(100,100,width,height); ctx.fillStyle = pattern; ctx.fill(); ctx.restore(); 

代码首先定义一个小的canvas,命名为tempCanvas,在tempCanvas上面绘制一个圆,需要注意的是tempCanvas的尺寸要设置为正好绘制下这个圆圈。

然后通过通过tempCanvas创建pattern对象,并把canvas的绘制上下文ctx的fillStyle指定为该pattern对象。
之后通过rect方法指定要fill的区域大小,改区域大小应该是所有最终要绘制的圆圈的大小的总和:var width = tempCanvas.width * 500,height = tempCanvas.height * 200;
最后调用画笔的fill方法,用tempCanvas填充区域。最终绘制的效果和绘制消耗的时间如下图所示:

 

关键字:
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信