javascript - Best practice for zooming and panning HTML5 canvas with > 10k objects - Stack Overflow

admin2025-04-04  0

I need to build kind of a map in canvas which displays over 10.000 elements (circles) and needs to be zoom- and panable. I described my approach here Android significantly slower in resizing and moving multiple canvas elements and changed my implementation on suggestions made in the ments.

To pan the map setTransform is now used on the canvas context and then all elements that are in the viewport are redrawn after the canvas was erased. (I get them out of an R-Tree). This happens on every mousemove event.

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

What would be the best practice to fulfil this requirement? My approach would be the following:

  • Have a viewport div over the canvas and make the canvas bigger (like 50% to each side)
  • Move the canvas in the div with topand leftstyling and redraw less frequently (when the canvas gets close to the viewport border)

I need to build kind of a map in canvas which displays over 10.000 elements (circles) and needs to be zoom- and panable. I described my approach here Android significantly slower in resizing and moving multiple canvas elements and changed my implementation on suggestions made in the ments.

To pan the map setTransform is now used on the canvas context and then all elements that are in the viewport are redrawn after the canvas was erased. (I get them out of an R-Tree). This happens on every mousemove event.

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

What would be the best practice to fulfil this requirement? My approach would be the following:

  • Have a viewport div over the canvas and make the canvas bigger (like 50% to each side)
  • Move the canvas in the div with topand leftstyling and redraw less frequently (when the canvas gets close to the viewport border)
Share Improve this question edited May 23, 2017 at 12:25 CommunityBot 11 silver badge asked Apr 21, 2017 at 13:12 Bernd StrehlBernd Strehl 2,9204 gold badges26 silver badges45 bronze badges 2
  • Do you have a lot of dynamic objects? As in, are there many objects moving in relation to other objects or is the content basically static? – Mike Cluck Commented Apr 21, 2017 at 14:41
  • @MikeC they won't move at all. I just might change the color of the circles depending on some ajax. – Bernd Strehl Commented Apr 21, 2017 at 14:48
Add a ment  | 

2 Answers 2

Reset to default 6

My approach would probably be:

  • Create an on-screen canvas the size of the "viewport" (the size of the browser window for instance)

  • Store the objects to draw in a data structure that lets you quickly determine which objects are visible at any given time (given the current viewport position and zoom).

  • Make sure the objects to draw (circles) are available as bitmaps (either loaded from image file or pre-rendered to a canvas if you draw them with canvas).
  • The circles's bitmap should be in the correct size given the zoom level, so pre-render the images to an off-screen canvas with the correct size when the zoom level changes. I'm assuming not all 10 000 points have unique images, that they all look the same or that there's only a handful variations.

Then on each render:

  • Clear the canvas
  • Call drawImage() for each circle being visible within the viewport, but only specify position, not width/height or use any transforms. The point being is that the image should only be "copied" to the viewport canvas 1-1. This is really fast.
  • I would suggest to not redraw on every mousemove event (or window resize etc.). Instead, use window.requestAnimationFrame() to schedule a redraw. That way you're not redrawing more than the browser's "refresh" rate, typically 60fps.
  • Panning in the viewport should be really fast since you're just calling drawImage() without any transformations for each visible circle. When you render and the zoom level has changed, there will be the cost of redrawing the pre-rendered images used as source for drawImage.

I second @Strilles answer.

here is a filtering example that switches between puting all sprites and puting visible-only sprites ever 5 seconds:

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

转载请注明原文地址:http://conceptsofalgorithm.com/Algorithm/1743710890a216102.html

最新回复(0)