Artem Zubkov
Artem Zubkov

Reputation: 559

How to optimize drawing a lot elements on canvas JS?

I have a lot of elements and mousemove method which, when selecting an item does redraw canvas.

function redraw(ctx) { // ctx - canvas context
    if (!needRedraw) 
        return;
    ctx.save();
    ctx.clearRect(0, 0, w, h);
    drawItems(ctx);
    ctx.restore();
}

function drawItems(ctx) {
    var l = nodes.length(); // array of elements for drawing (from 100 to 100000)
    for(var i = 0; i < l; i++) {
        var item = nodes[i];
        ctx.beginPath();
        ctx.strokeStyle = colors(item.type);
        ctx.fillStyle = fill(item);
        ctx.arc(item.x, item.y, item.r, 0, Math.PI * 2, true);
        ctx.fill();
        ctx.stroke();
        ctx.closePath();
    }
}

How to optimize this process, since it runs through all the elements of this highly expensive? May be used async method, but i'm not understand how to apply him?

Upvotes: 1

Views: 1363

Answers (4)

Jarrod
Jarrod

Reputation: 9465

Another performance tip: cache your Math.PI * 2 result.

As it is, javascript is getting PI from an object, then multiplying it. The value is always going to be the same so just add a constant

example

const PI_TIMES_TWO = 6.28; //or whatever degree of accuracy you want.

...

ctx.arc(item.x, item.y, item.r, 0, PI_TIMES_TWO, true);

Upvotes: 1

Jarrod
Jarrod

Reputation: 9465

You could also reverse your loop, assuming it's ok to draw everything in reverse order, as jsperf shows this to be quicker. A while loop is even quicker still.

example

var i = nodes.length();
while (i--) {
    // ...do stuff...
}

Upvotes: 1

Simon Sarris
Simon Sarris

Reputation: 63872

Your calls to save, restore and closePath do nothing as they are written. Remove them.

save and restore copy alllll of the canvas state and should be rarely if ever used. In a perfect performant world the only reason you'll ever need to use them is to reset the clipping region.

Draw one and only one path, and fill (and stroke) it only once, at the end.

Like this:

function redraw(ctx) { // ctx - canvas context
    if (!needRedraw) 
        return;
    ctx.clearRect(0, 0, w, h);
    drawItems(ctx);
}

function drawItems(ctx) {
    var l = nodes.length(); // array of elements for drawing (from 100 to 100000)
    ctx.beginPath(); //outside of loop!
    for(var i = 0; i < l; i++) {
        var item = nodes[i];
        ctx.moveTo(item.x+item.r,item.y); // set up subpath to be at the right point
        ctx.arc(item.x, item.y, item.r, 0, Math.PI * 2, true);
    }
    ctx.fill();     //outside of loop!
    ctx.stroke();   //outside of loop!
}

Depending on what you're doing its very possible there are more optimizations to be had, but that should help!

Hope your project goes well.

Upvotes: 6

John Ledbetter
John Ledbetter

Reputation: 14203

There's a great article on optimizing canvas operations, many of which might apply to you.

Probably the easiest to start with is moving the beginPath() and closePath() calls out of your loop, and instead use moveTo() in between the segments, as well as drawing your canvas off screen and then copying it over to the display canvas.

Upvotes: 2

Related Questions