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 comments.
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:
top
and left
styling and redraw less frequently (when the canvas gets close to the viewport border)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).
Then on each render:
I second @Strilles answer.
here is a filtering example that switches between computing all sprites and computing 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();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With