Best practice for scaling and panning HTML5 canvas with> 10k objects

I need to create a map view in canvas that displays over 10.000 elements (circles) and needs to be scalable and panoramic. I described my approach here Android is significantly slower in resizing and moving multiple canvas elements and changed my implementation based on the suggestions made in the comments.

The setTransform

canvas context is now used to pan the map , and then all elements that are in the viewport are redrawn after the canvas is deleted. (I get them from R-tree). This happens in every event mousemove

.

While it is really fast, when I have a zoomed map with ~ 200 objects to draw, panning is really slow when cut and over 10,000 objects to draw. I obviously need this too.

What would be the best practice to meet this requirement? My approach would be as follows:

  • You have a viewport div above the canvas and enlarge the canvas (e.g. 50% on each side).
  • Move canvas to div with top

    and left

    styles and redraw less often (when canvas gets close to viewport border)
+3


source to share


2 answers


My approach would probably be as follows:

  • Create a screen canvas the size of the "viewport" (for example, the size of the browser window)

  • Store objects for drawing in a data structure that allows you to quickly determine which objects will be visible at any given time (given the current position in the viewport and zoom).

  • Make sure the objects to draw (circles) are available as bitmaps (loaded from an image file, or preprocessed onto the canvas if you are drawing with the canvas).
  • The circles bitmap must be at the correct size for the zoom level, so pre-render the images on the screen canvas at the correct size when you change the zoom level. My guess is that not all 10,000 points have unique imagery, that they all look the same, or that there are only a few variations.


Then on each render:

  • Clear canvas
  • Call drawImage () for every circle visible in the viewport, but only specify the position, not the width / height, or use any transformations. The bottom line is that the image should only be "copied" to the canvas of the viewport 1-1. It's very fast.
  • I would suggest not redrawing all mousemove events (or window resizing, etc.). Use window.requestAnimationFrame () instead to schedule a redraw. This way you don't redraw more than the browser refresh rate, typically 60fps.
  • Panning in the viewport should be very fast as you simply call drawImage () without any transformations for each visible circle. When you change the render and zoom, the preprocessed images used as the source for drawImage will be redrawn.
+4


source


I second answer by @Strilles.

here is a filtering example that switches between evaluating all sprites and evaluating only sprites with only 5 seconds visible:



var canvas = document.body.appendChild(document.createElement("canvas"));
canvas.width = 100;
canvas.height = canvas.width;
var ctx = canvas.getContext("2d");
ctx.fillStyle = "rgba(255,0,0,0.1)";
;
var sprites = [];
while (sprites.length < 100000) {
    sprites.push({
        x: Math.round(Math.random() * 10000 - 5000),
        y: Math.round(Math.random() * 10000 - 5000)
    });
}
var drawAll = true;
function draw() {
    var targets;
    if (drawAll == true) {
        targets = sprites.slice(0);
    }
    else {
        targets = sprites.filter(function (sprite) {
            return sprite.x > -10 && sprite.x < 110 && sprite.y > -10 && sprite.y < 110;
        });
    }
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    for (var t = 0; t < targets.length; t++) {
        var target = targets[t];
        ctx.fillRect(target.x - 5, target.y - 5, 10, 10);
        ctx.strokeRect(target.x - 5, target.y - 5, 10, 10);
    }
}
function main() {
    requestAnimationFrame(main);
    for (var i = 0; i < sprites.length; i++) {
        var sprite = sprites[i];
        sprite.y++;
        if (sprite.y > 110) {
            sprite.y -= 200;
        }
    }
    draw();
}
setInterval(function () {
    drawAll = !drawAll;
    console.log(drawAll ? "Draw all" : "Draw filtered");
}, 5000);
main();
      

Run codeHide result


+1


source







All Articles