HTML5画布重绘速度极慢。

4

我刚开始尝试使用HTML5画布,希望能够用它制作一些游戏。然而,当我开始将鼠标坐标渲染到画布上时,它几乎停滞不前:

http://jsfiddle.net/mnpenner/zHpgV/

所有我做的就是呈现了38行和一些文本,应该可以处理,对吧?
我是否做错了什么?我想要能够呈现至少30 FPS,但对于这样的东西,我希望它能够绘制成千上万次。
还是我选择的工具不对?WebGL能胜任这项任务吗?为什么其中一个比另一个慢得多?

String.prototype.format = function() {
    var args = arguments;
    return this.replace(/\{(\d+)\}/g, function(m, n) {
        return args[n];
    });
};
var $canvas = $('#canvas');
var c = $canvas[0].getContext('2d');
var scale = 20;
var xMult = $canvas.width() / scale;
var yMult = $canvas.height() / scale;
var mouseX = 0;
var mouseY = 0;
c.scale(xMult, yMult);
c.lineWidth = 1 / scale;
c.font = '1pt Calibri';

function render() {
    c.fillStyle = '#dcb25c';
    c.fillRect(0, 0, scale, scale);
    c.fillStyle = '#544423';
    c.lineCap = 'square';
    for (var i = 0; i <= 19; ++i) {
        var j = 0.5 + i;
        c.moveTo(j, 0.5);
        c.lineTo(j, 19.5);
        c.stroke();
        c.moveTo(0.5, j);
        c.lineTo(19.5, j);
        c.stroke();
    }
    c.fillStyle = '#ffffff';
    c.fillText('{0}, {1}'.format(mouseX, mouseY), 0.5, 1.5);
}
render();
$canvas.mousemove(function(e) {
    mouseX = e.clientX;
    mouseY = e.clientY;
    render();
});
<canvas id="canvas" width="570" height="570"></canvas>

4个回答

11

这里是进行了大量优化后的代码。

http://jsfiddle.net/zHpgV/3/

以下是我所更改的需要考虑的内容:

  • 连续添加路径而不是停止并使用beginPath创建新路径。这是性能杀手中最大的因素。您最终会得到一个数千条线段的路径,它们永远不会被清除。
  • 在初始化时只需制作一次路径,而不是反复制作相同的路径。也就是说,在render内部您只需要调用stroke。您不需要再次调用lineTo/moveTo,特别是不需要连续地调用。详见注意事项1。
  • 为一个路径进行两次描边。
  • 在for循环内部进行描边。
  • 重新绘制背景而不是设置CSS背景。
  • 反复设置线帽。

注意1:如果您的应用程序中有多个路径,则应该像这个例子一样缓存路径,因为它们永远不会改变。关于如何做到这一点,我在这里有一篇教程。

当然,如果您只是为了创建一个背景,则应将其保存为png文件,并使用CSS背景图像。

就像这样:http://jsfiddle.net/zHpgV/4/

这时,您的渲染程序将非常简短:

function render() {
    c.clearRect(0, 0, scale, scale);
    c.fillText('{0}, {1}'.format(mouseX, mouseY), 0.5, 1.5);
}

我不知道路径可以这样工作!我认为拥有一个“路径”对象会更直观。现在明白为什么速度那么慢了,谢谢! - mpen
3
HTML5 Canvas规范中现在有一个路径对象,你将能够创建一个路径并在未来调用drawPath。但是目前没有浏览器实现它,可能需要数月时间才能使用。但总会有那么一天的! - Simon Sarris
我正在使用Adobe CC创建的画布。 因此,我看不到像“$canvas [0] .getContext('2d');”这样的东西。 我应该在哪里添加“beginPath”命令? - Nurullah Macun
@SimonSarris,你能帮我看看这个问题吗:https://dev59.com/W73pa4cB1Zd3GeqPaDkH - micronyks
您的教程链接无法访问,请更新一下,谢谢! - Meglio

9
正如我在评论中所说,我对这段代码的速度感到惊讶,因为我绘制了更加复杂的东西并进行了非常快速的动画,甚至不需要担心双缓冲。
所以我再仔细看了一下,果然发现了一个错误。
主要问题是绘图路径的累积。
每次绘制一个路径时,请添加 c.beginPath();
这里有一个相同内容的快速渲染示例,证明现在它可以飞快地运行。
Canvas 绘图是快速的,并且可以用于动画。

这个解决方案很好,但当你有500或1000次迭代的循环时它不起作用。 - micronyks

7

你不需要在每个动画帧中绘制整个网格。将其放在另一个底层画布上(通常称为“图层”,但它们只是单独的画布元素),这样你就可以仅重新绘制坐标。

<div id="canv">
 <canvas id="bgLayer" width="500" height="500" style="z-index: 0"></canvas>
 <canvas id="fgLayer"  width="500" height="500" style="z-index: 1"></canvas>
</div>

这里是我一直在使用分层画布进行操作的示例。表格绘制在底层画布上,小球绘制在顶层画布上。这只是一个游乐场,因此有很多需要修复和优化的地方,例如仅在另一个隐藏画布上绘制每个球,并使用getImageData/putImageData来提高性能。

此外,建议使用requestAnimationFrame更新画布。您的示例在每次鼠标移动时绘制,这比实际所需频繁得多(当然,当鼠标移动时)。

有一篇很好的文章介绍了如何提高画布性能。此外,这个主题还有一个很棒的SO帖子


我花了一些时间才明白你字面上的意思是将画布元素分层。我以为“层”是画布上下文中的一个概念。这是个好主意。谢谢你的建议! - mpen
抱歉,我改了一下以避免让其他人感到困惑。 - Artem Koshelev
1
这些是很好的评论,但在这种情况下我还发现另一个问题。我绘制了更加复杂的东西,并进行了非常快速的动画,甚至不需要担心双缓冲。 - Denys Séguret
我被Canvas卡住了。我的动画在Canvas上无法工作。你能帮我吗?这是我的问题:https://dev59.com/T5ffa4cB1Zd3GeqP1wfK - Learning-Overthinker-Confused

1

我遇到的问题与此处列出的答案不同。你能看出问题在哪里吗?

糟糕的代码

  const drawSegment = (key: number, lastAngle: number, angle: number) => {
    const ctx = canvasContext!;
    const value = segments[key];
    ctx.save();
    ctx.beginPath();
    ctx.moveTo(centerX, centerY);
    ctx.arc(centerX, centerY, size, lastAngle, angle, false);
    ctx.lineTo(centerX, centerY);
    ctx.closePath();
    ctx.fillStyle = segColors[key];
    ctx.fill();
    ctx.stroke();
    ctx.save();
    ctx.translate(centerX, centerY);
    ctx.rotate((lastAngle + angle) / 2);
    ctx.fillStyle = contrastColor;
    ctx.font = "bold 2em " + fontFamily;
    ctx.fillText(value.substring(0, 21), size / 2 + 20, 0);
    ctx.restore();
  };

这个示例代码的问题在于有两个调用了 ctx.save(),但只有一个调用了 ctx.restore()。这很难调试,因为它一开始可以正常工作,但突然间会变得非常缓慢。

ctx.save() 在堆栈上创建一个新的条目,而 ctx.restore() 从堆栈中弹出该条目。因此,如果您有一个随着时间不断增长的无限大的堆栈,它最终将达到一个极限,从而使浏览器变慢。

更多信息:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/save

已修复

  const drawSegment = (key: number, lastAngle: number, angle: number) => {
    const ctx = canvasContext!;
    const value = segments[key];
    ctx.save();
    ctx.beginPath();
    ctx.moveTo(centerX, centerY);
    ctx.arc(centerX, centerY, size, lastAngle, angle, false);
    ctx.lineTo(centerX, centerY);
    ctx.closePath();
    ctx.fillStyle = segColors[key];
    ctx.fill();
    ctx.stroke();
    // ctx.save(); <-- get rid of this line of code!
    ctx.translate(centerX, centerY);
    ctx.rotate((lastAngle + angle) / 2);
    ctx.fillStyle = contrastColor;
    ctx.font = "bold 2em " + fontFamily;
    ctx.fillText(value.substring(0, 21), size / 2 + 20, 0);
    ctx.restore();
  };

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接