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

结果绘制出了根据行列排布的许多个圆圈了,以下图所示:
《canvas高效绘制10万图形,你必需晓得的高效绘制技能》

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

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

时刻显现为几百毫秒(3到4百毫秒),以下图所示:
《canvas高效绘制10万图形,你必需晓得的高效绘制技能》

几百毫秒的绘制时刻,必定是卡顿的。想要流通操纵,一定还的优化。

批量绘制

起首想到的是批量绘制,前面的代码中,每次变量都邑挪用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倍摆布,以下图所示:
《canvas高效绘制10万图形,你必需晓得的高效绘制技能》

须要注重的是上述代码中的moveTo语句:

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

这是因为: 当运用arc要领给途径中增加子途径的时刻,arc所定义的途径会自动和途径鸠合中的末了一个途径衔接起来,以下图所示:
《canvas高效绘制10万图形,你必需晓得的高效绘制技能》

此处的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添补地区。终究绘制的结果和绘制斲丧的时刻以下图所示:
《canvas高效绘制10万图形,你必需晓得的高效绘制技能》

经由过程上图可以看出,效力极高,可以到达零点几毫秒的级别。

新的需求

假如客户需求只是这么简朴,置信运用canvas pattern对象这类体式格局,效力是最高的。然则,客户的现实需求是,先绘制10万个的圆圈,然后可以用擦除东西,擦除一些地区的圆圈,以下图所示:
《canvas高效绘制10万图形,你必需晓得的高效绘制技能》

原始绘制要领和批量绘制要领如果完成上述结果,都很轻易,只要把不须要绘制圆圈的位置,直接疏忽掉即可以。

比如用一个map纪录须要疏忽的圆圈的坐标,遍历的时刻推断在map纪录中的处所就直接跳过不举行绘制操纵。

canvas pattern + 裁剪

假如是canvas pattern的体式格局,应当怎样完成上图的结果呢? 经由思索发明可以经由过程ctx.clip要领。

clip,裁剪。假如经由过程ctx.clip定义了裁剪地区,绘制的图形只会在裁剪地区的部份显现出来,裁剪地区以外的,则不会显现。

没一个圆圈都邑占用一个矩形地区,本案例中,可以把要显现的的圆圈所占的矩形地区都定义到裁剪地区内里,而不要显现的圆圈的矩形地区则消除到裁剪地区以外,以下图所示,绘制圆圈的矩形地区用实线示意出来,不绘制圆圈的地区用虚线示意:
《canvas高效绘制10万图形,你必需晓得的高效绘制技能》

只须要把一切实线示意的矩形地区都增加到要clip的途径中去,然后挪用fill要领,则只会在完成定义的矩形地区显现出来圆圈。以下是示例代码:

 for(var i = 0;i < 400; i ++){
                    for(var j = 0;j < 400;j ++){
                            var r = Math.random();
                             if(r <0.2){
                              templateMap[i+":" + j] = true;
                              continue;
                            }
                              
                          var x = 10 + j * tempCanvas.width;
                          var y = 10 + i * tempCanvas.height;
                          var rect = {
                            x : x,
                            y : y,
                            width : tempCanvas.width,
                            height:tempCanvas.height
                          };
                         ctx.rect(rect.x,rect.y,rext.width,rect.height);
   }
ctx.clip();

起首遍历一切的圆圈坐标,为了演示结果,用Math.random为了模仿随机发生一个数,假如这个数小于0.2,则当前圆圈的矩形地区不会被到场裁剪地区,也就是该圆圈不会显现出来。
经由过程上面裁剪操纵后,“擦除后的结果”算是完成了。然则,经由测试,机能却低徊去了,为何,因为增加了许多rect操纵。测试下来,一幁的绘制时刻大概在80多毫秒,比批量绘制照样高一点,然则觉得照样不够好。

Pattern + 兼并裁剪

视察上面 “裁剪地区” 这个图,以第一行动例,第一、第二、第三个矩形地区是连在一块的,完整没有必要挪用三次ctx.rect要领,而是先用算法把三个地区兼并为一个矩形地区,然后挪用一次ctx.rect要领即可,以下图:
《canvas高效绘制10万图形,你必需晓得的高效绘制技能》
下面是兼并裁剪地区的算法,现在只是完成了统一行的兼并,越发优化的兼并算法并没有完成,代码以下:

 function calRectMap (tempCanvas){
                    if(rectMap != null){
                      return;
                    }
                    rectMap = rectMap || [];
                     for(var i = 0;i < 400; i ++){
                      for(var j = 0;j < 400;j ++){
                            var r = Math.random();
                             if(r <0.2){
                              templateMap[i+":" + j] = true;
                              continue;
                            }
                              
                          var x = 10 + j * tempCanvas.width;
                          var y = 10 + i * tempCanvas.height;
                          var rect = {
                            x : x,
                            y : y,
                            width : tempCanvas.width,
                            height:tempCanvas.height
                          };
                          lineRectMap[i] = lineRectMap[i] || [];

                          lineRectMap[i][j] = rect;
                      }
                      unionLineRects(lineRectMap[i],rectMap);
                    }
               }

               function unionLineRect(rect1,rect2){
                    return {
                        x: rect1.x,
                        y : rect1.y,
                        width:rect1.width + rect2.width,
                        height:rect1.height
                    }
               }

               function unionLineRects(lineRectMap,rectMap){
                    var lastRect = null,lastNotNullIndex = null;
                    for(var j = 0;j < 400;j ++){
                        
                        var currentRect = lineRectMap[j];
                        if(lastRect == null){
                              lastRect = currentRect;
                        }else{
                            if( lastNotNullIndex == j - 1 && currentRect){
                                lastRect = unionLineRect(lastRect,currentRect);
                            }
                        }
                        if(currentRect != null){
                          lastNotNullIndex = j;
                        }else if (lastRect){
                            rectMap.push(lastRect);
                            lastNotNullIndex = null;
                            lastRect = null;
                        }
                    }
                    if(lastRect){
                      rectMap.push(lastRect);
                    }
               }

相干兼并的算法,此处不再细致申明。 兼并以后,测试绘制的时刻下降到了10几毫秒,算是比较好的绘制结果了:
《canvas高效绘制10万图形,你必需晓得的高效绘制技能》

webgl绘制

因为笔者本人也历久研讨webgl的手艺,所以尝试着用webgl实线了2d的绘制,相干细节不在此处赘述,背面会写特地的文章如何用webgl绘制2d图形。终究测试的效力不是很抱负,差不多100多毫秒,和上面的批量绘制差不多。 因为用webgl绘制,单次的绘制效力应当不会太差,然则因为须要遍历挪用10万次绘制敕令,必定效力不高。别的webgl绘制的结果现实上是没有2d绘制的结果好的,锯齿严峻。 要完成好的结果,还须要引入去锯齿相干手艺。 绘制的结果以下:
《canvas高效绘制10万图形,你必需晓得的高效绘制技能》
用webgl绘制2d图形的相干主题,转头会别的写一篇文章引见。敬请关注。

webgl2绘制

webgl2 引入了实例化数组,经由过程这个功用,可以完成把许屡次的绘制挪用兼并为一个绘制挪用,这会极大进步绘制效力。

有关实例化数组的功用,参考
https://www.jianshu.com/p/d40…

绘制10万个圆形的效力大概在每帧零点零几毫秒,几乎就是大boss级别的快,以下图:

《canvas高效绘制10万图形,你必需晓得的高效绘制技能》

跋文

经由过程这篇文章,除了想给读者通报相干学问点以外,实在还想表达一个看法:
比拟于学问点,程序员越发须要磨炼的是底层思维才能。在我看来,底层思维才能包含:进修力、创造力、推断力和思索力。而勤于思索的人,不拘泥于屡见不鲜,都可以从一样平常死板的使命中发明许多风趣的东西,启示更多深切的思绪。
勤于思索是很主要的。 学问是死的,人是活的,一样的学问点,在思索力强的人手上,就可以延伸出许多好的处理计划。
这就请求人勤于探究,不要满足于把使命完成,而是要多深切思索,多总结,探究更多的计划和能够性。这自身有助于磨炼思索力和创造力,而思索力和创造力又会反过来协助你处理更多的题目。

实在IT行业的学问更新越来越快,可以以不变应万变的人,就是具有优越的进修力、创造力、推断力和思索力的人。这些才能会让你在变更万千的手艺海洋中,挺立不倒,不被吞没。

固然,标书能够有点好为人师了。 在一样平常的工作中,彪叔更喜好做的事变,就是启示部属的思索,而不仅仅是某个题目的处理计划。这是比进修学问越发主要的素养。彪叔也会在我的其他文章中,分享底层才能的相干认知。有兴致的猿们可以关注彪叔的公号:ITman彪叔

迎接关注民众号:
《canvas高效绘制10万图形,你必需晓得的高效绘制技能》

    原文作者:netcy
    原文地址: https://segmentfault.com/a/1190000017133772
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞