在画布上绘制10,000个对象javascript

时间:2014-05-05 08:37:51

标签: javascript html5 performance canvas drawimage

我需要在画布上绘制超过10,000张图像(32x32像素),但超过2000张图表的表现非常糟糕。

这是一个小例子:

对象结构{position:0}

for(var nObject = 0; nObject < objects.length; nObject++){
    ctx.save();
    ctx.translate(coords.x,coords.y);
    ctx.rotate(objects[nObject].position/100);
    ctx.translate(radio,0);
    ctx.drawImage(img,0,0);
    ctx.restore();
    objects[nObject].position++;
}

使用此代码我可以对坐标周围的图像进行扫描。

您建议如何提高效果?

更新

我尝试分层,但表现恶化

http://jsfiddle.net/72nCX/3/

5 个答案:

答案 0 :(得分:46)

我可以给你10,000,但有两个主要缺点。

  1. 您可能会注意到图像完全不尊重透明度,可以修复..但这超出了此答案的范围。

  2. 您必须使用数学进行任何类型的转换,因为标准画布转换矩阵无法应用于ImageData

  3. Live Demo

    代码和方法的说明

    因此,要使用画布和大量对象获得最快性能,您需要使用 ImageData 。这基本上是在每像素级别访问canvas元素,并允许你做各种很酷的东西。我使用了两种主要方法。

    此外还有一个nice tutorial,它可以帮助您更好地理解它。

    所以我做的是首先为图像

    创建一个临时画布
    imgToDraw.onload = function () {
        // In memory canvas
        imageCanvas = document.createElement("canvas"),
        iCtx = imageCanvas.getContext("2d");
    
        // set the canvas to the size of the image
        imageCanvas.width = this.width;
        imageCanvas.height = this.height;
    
        // draw the image onto the canvas
        iCtx.drawImage(this, 0, 0);
    
        // get the ImageData for the image.
        imageData = iCtx.getImageData(0, 0, this.width, this.height);
        // get the pixel component data from the image Data.
        imagePixData = imageData.data;
    
        // store our width and height so we can reference it faster.
        imgWidth = this.width;
        imgHeight = this.height;
    
        draw();
    };
    

    下一个是渲染功能中的主要部分

    我刚发布相关部分。

    // create new Image data. Doing this everytime gets rid of our 
    // need to manually clear the canvas since the data is fresh each time
    var canvasData = ctx.createImageData(canvas.width, canvas.height),
        // get the pixel data
        cData = canvasData.data;
    
    // Iterate over the image we stored 
    for (var w = 0; w < imgWidth; w++) {
        for (var h = 0; h < imgHeight; h++) {
            // make sure the edges of the image are still inside the canvas
            // This also is VERY important for perf reasons
            // you never want to draw outside of the canvas bounds with this method
            if (entity.x + w < width && entity.x + w > 0 &&
                entity.y + h > 0 && entity.y + h < height) {
    
                // get the position pixel from the image canvas
                var iData = (h * imgWidth + w) * 4;
    
                // get the position of the data we will write to on our main canvas
                // the values must be whole numbers ~~ is just Math.floor basically
                var pData = (~~ (entity.x + w) + ~~ (entity.y + h) * width) * 4;
    
                // copy the r/g/b/ and alpha values to our main canvas from 
                // our image canvas data.
    
                cData[pData] = imagePixData[iData];
                cData[pData + 1] = imagePixData[iData + 1];
                cData[pData + 2] = imagePixData[iData + 2];
                // this is where alpha blending could be applied
                if(cData[pData + 3] < 100){
                    cData[pData + 3] = imagePixData[iData + 3];
                }
            }
        }
    }
    
    // now put all of that image data we just wrote onto the actual canvas.
    ctx.putImageData(canvasData, 0, 0);
    

    主要的一点是,如果你需要在画布上绘制一些荒谬的对象,你不能使用drawImage,像素操作就是你的朋友。

答案 1 :(得分:4)

我认为this就是您所需要的。

Eric Rowell(KineticJS的创始人)在这里做了一些压力测试。

他说:

&#34;创建10个图层,每个图层包含1000个形状,以创建10,000个形状。这大大提高了性能,因为在从图层中移除圆圈而不是所有10,000个形状时,只需要绘制1,000个形状。&#34;

&#34;请记住,拥有太多图层也会降低性能。我发现使用由1,000个形状组成的10个层比使用500个形状的20个层或者具有2,000个形状的5个层更好。&#34;

更新:您需要运行测试用例,其中最优化的过程适合您。 示例:可以通过以下任一方式实现10000个形状:

  

10000个形状* 1层

     

5000个形状* 2层

     

2500个形状* 4层

无论哪种方式适合您,请选择它! 这取决于你的代码。

答案 2 :(得分:1)

如果图像不重叠,则生成的图像为3200x3200像素,大于大多数显示器可显示的像素。因此,您可以尝试获取已转换图像的边界框并跳过可见区域之外的那些(即使画布应该已经为您执行此操作)。

另一个想法是将小图像组合成更大的图像并将它们组合在一起。

如果你想组织环形图像,那么你可以将它们作为一个环绘制一次,将其保存为图像,然后旋转环形图像&#34;而不是每个单独的图像。

最后,看看WebGL可能比2D canvas API更有效。

答案 3 :(得分:1)

您可以采取以下步骤来提高效果:

  • 首先摆脱save / restore - 这是非常昂贵的电话,可以替换为setTransform
  • 展开循环以在每次迭代中执行更多操作
  • 缓存所有属性

<强> FIDDLE

循环解开4次迭代的示例:

for(var nObject = 0,
        len = objects.length,    // cache these
        x = coords.x,
        y = coords.y; nObject < len; nObject++){

    ctx.setTransform(1,0,0,1, x, y);   // sets absolute transformation
    ctx.rotate(objects[nObject].position*0.01);
    ctx.translate(radio,0);
    ctx.drawImage(imgToDraw,0,0);
    objects[nObject++].position++;

    ctx.setTransform(1,0,0,1,x, y);
    ctx.rotate(objects[nObject].position*0.01);
    ctx.translate(radio,0);
    ctx.drawImage(imgToDraw,0,0);
    objects[nObject++].position++;

    ctx.setTransform(1,0,0,1,x, y);
    ctx.rotate(objects[nObject].position*0.01);
    ctx.translate(radio,0);
    ctx.drawImage(imgToDraw,0,0);
    objects[nObject++].position++;

    ctx.setTransform(1,0,0,1,x, y);
    ctx.rotate(objects[nObject].position*0.01);
    ctx.translate(radio,0);
    ctx.drawImage(imgToDraw,0,0);
    objects[nObject++].position++;
}
ctx.setTransform(1,0,0,1,0,0);  // reset transform for rAF loop

(不要期望实时表现)。

尽管如此,在如此小的区域内绘制2000个物体可能有点毫无意义。如果您正在使用效果,我建议采用这种方法:

  • 创建离屏画布
  • 使用上述方法制作5-8帧并将其存储为图像
  • 按原样播放5-8张图片,而不是进行所有计算

如果你需要更多流畅的外观,只需生成更多的框架。您可以根据稍后用作精灵表格的单元格将每个帧存储在单个画布中。在绘制时,当然必须注意当前位置是静态的而不是实际动画时的移动。旋转和产生的位置是另一个因素。

答案 4 :(得分:1)

经过各种测试,我得出以下结论:

  • canvas没有此任务的容量。
  • 分层画布仅在不需要不断重绘静态元素时才有助于提高性能。
  • 添加坐标打印限制有助于渲染。
  • 慢功能的替代方案
  • 不打印元素最终会被另一个具有更高z-index的元素隐藏(处理它)。

最终结果是所有贡献的小混合。但需要改进。

使用30,000个对象进行测试,性能保持在60 / fps。

http://jsfiddle.net/NGn29/1/

        var banPrint = true;
        for(nOverlap = nObject; nOverlap < objects.length; nOverlap++){
            if(
                objects[nOverlap].position == objects[nObject].position
                && nOverlap != nObject
            ){
                banPrint = false;
                break;
            }
        }