使用 > 10k 对象缩放和平移 HTML5 画布的最佳实践

Str*_*rnd 5 javascript canvas html5-canvas

我需要在画布中构建一种地图,它显示超过 10.000 个元素(圆圈)并且需要缩放和平移。我在这里描述了我的方法Android 在调整大小和移动多个画布元素方面明显变慢,并根据评论中提出的建议更改了我的实现。

setTransform现在在画布上下文中使用平移地图,然后在擦除画布后重新绘制视口中的所有元素。(我将它们从 R 树中取出)。这发生在每个mousemove事件上。

虽然当我有一个包含大约 200 个要绘制的对象的缩放地图时这真的很快,但在缩小时平移真的很慢并且需要绘制超过 10k 个对象。我显然也需要它快。

满足此要求的最佳实践是什么? 我的方法如下:

  • 在画布上有一个视口 div 并使画布更大(比如每边 50%)
  • 使用topleft样式移动 div 中的画布并减少重绘的频率(当画布靠近视口边框时)

Str*_*lle 6

我的方法可能是:

  • 创建一个“视口”大小的屏幕画布(例如浏览器窗口的大小)

  • 将要绘制的对象存储在数据结构中,以便您可以快速确定在任何给定时间(给定当前视口位置和缩放)可见的对象。

  • 确保要绘制的对象(圆圈)可用作位图(从图像文件加载或预渲染到画布,如果您使用画布绘制它们)。
  • 给定缩放级别,圆圈的位图应该具有正确的大小,因此当缩放级别更改时,将图像预渲染到具有正确大小的屏幕外画布。我假设并非所有 10 000 个点都有独特的图像,它们看起来都一样,或者只有少数变化。

然后在每次渲染时:

  • 清除画布
  • 为每个在视口中可见的圆调用 drawImage(),但只指定位置,而不是宽度/高度或使用任何变换。关键是图像应该只“复制”到视口画布 1-1。这真的很快。
  • 我建议不要在每个 mousemove 事件(或窗口大小调整等)上重绘。相反,使用 window.requestAnimationFrame() 来安排重绘。这样你的重绘不会超过浏览器的“刷新”率,通常是 60fps。
  • 在视口中平移应该非常快,因为您只是调用 drawImage() 而没有对每个可见圆进行任何转换。当您渲染并且缩放级别发生变化时,将需要重新绘制用作 drawImage 源的预渲染图像的成本。


Emi*_*sen 5

我第二次@Strilles 回答。

这是一个过滤示例,它每 5 秒在计算所有精灵和计算仅可见精灵之间切换:

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 Code Online (Sandbox Code Playgroud)