我创建了画布(800x400)-并用网格填充了它。它可以工作,但是网格(线)的渲染大约需要3秒钟,这似乎太长了。我做错什么了吗?




 var drawGrid = function(w, h, id) {
    var canvas = document.getElementById(id);
    var ctx = canvas.getContext('2d');
    ctx.canvas.width  = w;
    ctx.canvas.height = h;


    for (x=0;x<=w;x+=20) {
        for (y=0;y<=h;y+=20) {
            ctx.moveTo(x, 0);
            ctx.lineTo(x, h);
            ctx.stroke();
            ctx.moveTo(0, y);
            ctx.lineTo(w, y);
            ctx.stroke();
        }
    }

    };


    drawGrid(800, 400, "grid"); 

 <canvas id="grid"></canvas> 




评论

您是否尝试过在画布上使用背景图片?您应该能够使用重复的SVG(可变大小)背景。

不,我没有。我会尝试看看区别。

3秒真是疯狂!电脑是什么,那是我的即时信息。

#1 楼

Quill已经很好地加快了代码的速度,因此,我将重点介绍您现在拥有的代码。

给我这个对象!
(不是如何获得它)
/>您的drawGrid函数采用了该函数希望自己找到的DOM元素的id。但是,这不是一个好习惯。相反,您应该传递DOM元素本身。如果使用得当,这也可以大大提高代码的效率。
要详细了解为什么它是不好的做法,请这样考虑:如果在找到元素之前需要对元素进行一些特定的检查,您已准备好将其提供给函数,应该如何将准备好的元素传递给函数?
不要让函数担心如何获取所需的内容;只需提供所需的内容即可。

获取我的上下文信息!现在就扔掉吧!

var ctx = canvas.getContext('2d');


每次调用函数时都会创建一个。假设此代码中将进行更多绘制,为什么不将画布和上下文都放在全局范围内呢?找到上下文,将其放在变量中,然后在每次调用它时在函数末尾销毁它没有意义。

询问上下文所在的画布。然后,询问画布的位置。

var canvas = document.getElementById(id);
...
ctx.canvas.width  = w;
ctx.canvas.height = h;


这绝对是零意义。首先,使用画布获取ctx。然后,使用ctx通过访问其属性再次获取canvas。为什么不仅仅使用您刚定义的canvas

总是有另一种方式
是的,当前形成网格的方式非常慢。正如Quill所示,还有其他选择:

将其渲染为SVG(Quill已经显示了此内容)
获取单个图像作为握把的一部分,然后复制面食您所需区域周围的图像。
绘制正方形。

我没有测试这些,所以我不知道它们提供多少速度提升(如果有)。但是,请随时尝试其中的一些。

评论


\ $ \ begingroup \ $
谢谢。您能否阐明您的第一点“给我对象”。您是在说我应该完全使用JavaScript来创建canvas元素还是在函数定义中硬编码canvas id?我想使它尽可能通用,以重用相同的功能在同一网站上创建其他画布。不鼓励吗?谢谢
\ $ \ endgroup \ $
–荒原
15年12月22日在10:58

\ $ \ begingroup \ $
顺便说一句,我可能应该澄清一下,我正在做一个数学网站,以说明/模拟一些数学概念供学习者练习。
\ $ \ endgroup \ $
–荒原
15年12月22日在11:00

\ $ \ begingroup \ $
@Wasteland我是说您应该将已经找到的JavaScript对象传递给函数,而不仅仅是ID。
\ $ \ endgroup \ $
– SirPython
15年12月22日在22:44

\ $ \ begingroup \ $
过多的图纸也无助于提高性能
\ $ \ endgroup \ $
– Deian
16年7月19日在19:00

\ $ \ begingroup \ $
我可能是不正确的,但是我相信您应该在每帧请求上下文,因为上下文在下一帧无效。不过,这可能是我正在使用的其他系统。
\ $ \ endgroup \ $
–埃里克·布莱德
16年7月20日在5:25

#2 楼

尝试在画布上使用SVG对象:

<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
    <defs>
        <pattern id="smallGrid" width="8" height="8" patternUnits="userSpaceOnUse">
            <path d="M 8 0 L 0 0 0 8" fill="none" stroke="gray" stroke-width="0.5" />
        </pattern>
        <pattern id="grid" width="80" height="80" patternUnits="userSpaceOnUse">
            <rect width="80" height="80" fill="url(#smallGrid)" />
            <path d="M 80 0 L 0 0 0 80" fill="none" stroke="gray" stroke-width="1" />
        </pattern>
    </defs>

    <rect width="100%" height="100%" fill="url(#smallGrid)" />
</svg>





而将fill="url(#smallGrid)"更改为fill="url(#grid)"会产生以下结果:



然后您可以像下面那样将其放入画布中:




 var drawGrid = function(w, h, id) {
    var canvas = document.getElementById(id);
    var ctx = canvas.getContext('2d');
    ctx.canvas.width  = w;
    ctx.canvas.height = h;
    
    var data = '<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg"> \
        <defs> \
            <pattern id="smallGrid" width="8" height="8" patternUnits="userSpaceOnUse"> \
                <path d="M 8 0 L 0 0 0 8" fill="none" stroke="gray" stroke-width="0.5" /> \
            </pattern> \
            <pattern id="grid" width="80" height="80" patternUnits="userSpaceOnUse"> \
                <rect width="80" height="80" fill="url(#smallGrid)" /> \
                <path d="M 80 0 L 0 0 0 80" fill="none" stroke="gray" stroke-width="1" /> \
            </pattern> \
        </defs> \
        <rect width="100%" height="100%" fill="url(#smallGrid)" /> \
    </svg>';

    var DOMURL = window.URL || window.webkitURL || window;
    
    var img = new Image();
    var svg = new Blob([data], {type: 'image/svg+xml;charset=utf-8'});
    var url = DOMURL.createObjectURL(svg);
    
    img.onload = function () {
      ctx.drawImage(img, 0, 0);
      DOMURL.revokeObjectURL(url);
    }
    img.src = url;
}
drawGrid(800, 400, "grid"); 

 <canvas id="grid"></canvas> 





当然,您需要尝试调整网格的大小以匹配所需的内容。但是,您会发现此方法要快得多。

评论


\ $ \ begingroup \ $
预先准备数据,将数据blob并对其进行所有处理可能会更快(可能是初始化过程的一部分)。然后,当需要绘制数据时,它应该更快,因为数据将准备就绪。
\ $ \ endgroup \ $
– SirPython
2015年12月22日,0:43

\ $ \ begingroup \ $
谢谢Quill-我对SVG的经验非常有限-可以轻松地用js操作吗?
\ $ \ endgroup \ $
–荒原
15/12/22在11:08

\ $ \ begingroup \ $
SVG与HTML非常相似,它遵循的类似XML的结构,是的,它可以轻松地用JavaScript进行操作,并且像示例中一样,可以轻松地将其自身填充为背景。
\ $ \ endgroup \ $
– Quill
15/12/22在11:10

\ $ \ begingroup \ $
我建议将SVG标记移动到类似script标签的位置。这样,您的SVG将不会出现在JS中。如果可以使用ES6,则另一种选择是模板字符串。至少这避免了每行末尾的\。
\ $ \ endgroup \ $
–约瑟夫
2015年12月22日13:00



#3 楼

您多次渲染同一条线。

要创建网格,您需要绘制(w / stepSize)垂直线和(h / stepSize)水平线。
总计:( w + h)/ stepSize行

您使用了两个嵌套循环,并绘制了:(w / stepSize)*(h / stepSize)=(w * h)/(stepSize * stepSize)行

为您介绍一些示例,请参见以下示例:

对于w = 1000,h = 1000,stepSize = 20。
您的函数将绘制2500条线,其中只有100条线

对于w = 800,h = 400,stepSize = 20。
您的函数将绘制800条线,而只有60条线就足够。

在此外,您应该将canvas调整大小放在该函数之外,并且在程序开始时仅执行一次!




 // the canvas logic should be done once somewhere else 
var w = 800;
var h = 400;
// grid step
var step = 20; 
var canvasElementId = 'grid';


var canvas = document.getElementById(canvasElementId);
// this is how you resize the canvas
canvas.width  = w;
canvas.height = h;

var ctx = canvas.getContext('2d');


// the render logic should be focusing on the rendering 
var drawGrid = function(ctx, w, h, step) {
    ctx.beginPath(); 
    for (var x=0;x<=w;x+=step) {
            ctx.moveTo(x, 0);
            ctx.lineTo(x, h);
    }
    // set the color of the line
    ctx.strokeStyle = 'rgb(255,0,0)';
    ctx.lineWidth = 1;
    // the stroke will actually paint the current path 
    ctx.stroke(); 
    // for the sake of the example 2nd path
    ctx.beginPath(); 
    for (var y=0;y<=h;y+=step) {
            ctx.moveTo(0, y);
            ctx.lineTo(w, y);
    }
    // set the color of the line
    ctx.strokeStyle = 'rgb(20,20,20)';
    // just for fun
    ctx.lineWidth = 5;
    // for your original question - you need to stroke only once
    ctx.stroke(); 
};


drawGrid(ctx, w, h, step); 

 <canvas id="grid"></canvas> 




评论


\ $ \ begingroup \ $
欢迎使用代码审查!恭喜您写出了一个很好的答案,请随时坚持:-)
\ $ \ endgroup \ $
–桅杆
16年7月19日在20:11

\ $ \ begingroup \ $
我读过的第一个好答案
\ $ \ endgroup \ $
–多米尼克
20/11/29在9:52

#4 楼

您无缘无故地重复运行三个语句。尝试以下操作:

var drawGrid = function(w, h, id) {
    var canvas = document.getElementById(id);
    var ctx = canvas.getContext('2d');
    ctx.canvas.width = w;
    ctx.canvas.height = h;


    for (x = 0; x <= w; x += 20) {
        ctx.moveTo(x, 0);
        ctx.lineTo(x, h);
        for (y = 0; y <= h; y += 20) {
            ctx.moveTo(0, y);
            ctx.lineTo(w, y);
        }
    }
    ctx.stroke();

};


drawGrid(800, 400, "grid");


注意,我将内部for循环的前3条语句移到了哪里。差异很大。

评论


\ $ \ begingroup \ $
我真的不明白-为什么您需要嵌套循环?
\ $ \ endgroup \ $
– Deian
16年7月20日在20:09

\ $ \ begingroup \ $
@deian内循环是绘制垂直线。根据此处其他人的建议,您可以将ctx.stroke()移至循环之外。
\ $ \ endgroup \ $
–将
16-09-17在22:10

#5 楼

一个有趣的问题,赏金显然应该去死。

似乎没有人注意到xy是全局变量。

除此之外,我将使用line函数(速度稍慢,但阅读起来更直观)。

//Draw the line on the context, caller needs to worry about `stroke()`
function pencilLine( ctx, fromX, fromY, toX, toY ){
  ctx.moveTo( fromX, fromY );
  ctx.lineTo( toX, toY );
}


评论


\ $ \ begingroup \ $
为什么不在函数中也包含ctx.stroke()?
\ $ \ endgroup \ $
– 200_success
16年7月20日在16:45

\ $ \ begingroup \ $
因为如果您绘制200条线,那么您就不想进行200划。
\ $ \ endgroup \ $
– konijn
16年7月20日在16:48

\ $ \ begingroup \ $
我倾向于用铅笔在那些打算大量使用的例程之前加上前缀,以表明它们不会在行中出现。
\ $ \ endgroup \ $
– konijn
16年7月20日在16:48

\ $ \ begingroup \ $
@konijn x,y的荣誉当然应该是本地变量!
\ $ \ endgroup \ $
– Deian
16年7月20日在20:08