HTML5 / Canvas是否支持双缓冲?

时间:2010-05-08 19:05:10

标签: javascript html5 canvas double-buffering

我想要做的是在缓冲区上绘制我的图形,然后将其原样复制到画布上,这样我就可以做动画并避免闪烁。但我找不到这个选项。有谁知道我怎么能这样做?

12 个答案:

答案 0 :(得分:82)

一种非常简单的方法是在同一屏幕位置放置两个canvas元素,并为需要显示的缓冲区设置可见性。完成后绘制隐藏和翻转。

一些代码:

CSS:

canvas { border: 2px solid #000; position:absolute; top:0;left:0; 
visibility: hidden; }

翻阅JS:

Buffers[1-DrawingBuffer].style.visibility='hidden';
Buffers[DrawingBuffer].style.visibility='visible';

DrawingBuffer=1-DrawingBuffer;

在此代码中,数组'Buffers []'包含两个canvas对象。因此,当您想要开始绘图时,您仍需要获取上下文:

var context = Buffers[DrawingBuffer].getContext('2d');

答案 1 :(得分:29)

以下有用链接除了显示使用双缓冲的示例和优点外,还显示了使用html5 canvas元素的其他一些性能提示。它包含指向jsPerf测试的链接,这些测试将跨浏览器的测试结果聚合到Browserscope数据库中。这可确保验证性能提示。

http://www.html5rocks.com/en/tutorials/canvas/performance/

为了您的方便,我已经包含了文章中描述的有效双缓冲的最小示例。

// canvas element in DOM
var canvas1 = document.getElementById('canvas1');
var context1 = canvas1.getContext('2d');

// buffer canvas
var canvas2 = document.createElement('canvas');
canvas2.width = 150;
canvas2.height = 150;
var context2 = canvas2.getContext('2d');

// create something on the canvas
context2.beginPath();
context2.moveTo(10,10);
context2.lineTo(10,30);
context2.stroke();

//render the buffered canvas onto the original canvas element
context1.drawImage(canvas2, 0, 0);

答案 2 :(得分:18)

我已经测试过的浏览器都会通过不重新绘制画布来处理这个缓冲,直到绘制框架的代码完成为止。另请参阅WHATWG邮件列表:http://www.mail-archive.com/whatwg@lists.whatwg.org/msg19969.html

答案 3 :(得分:11)

你总能这样做 var canvas2 = document.createElement("canvas"); 并且根本不将它附加到DOM。

只是说因为你们似乎对display:none;如此着迷 它对我来说似乎更干净,并且比仅仅拥有一个笨拙的看不见的画布更准确地模仿双缓冲方式的想法。

答案 4 :(得分:7)

两年多以后:

不需要'手动'实现双缓冲。吉瑞先生在他的书"HTML5 Canvas"中写到了这一点。

有效减少闪烁使用requestAnimationFrame()

答案 5 :(得分:6)

对于不信的人来说,这里有一些闪烁的代码。请注意,我明确清除以删除上一个圆圈。

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");

function draw_ball(ball) {
    ctx.clearRect(0, 0, 400, 400);
    ctx.fillStyle = "#FF0000";
    ctx.beginPath();
    ctx.arc(ball.x, ball.y, 30, 0, Math.PI * 2, true);
    ctx.closePath();
    ctx.fill();
}

var deltat = 1;
var ball = {};
ball.y = 0;
ball.x = 200;
ball.vy = 0;
ball.vx = 10;
ball.ay = 9.8;
ball.ax = 0.1;

function compute_position() {
    if (ball.y > 370 && ball.vy > 0) {
        ball.vy = -ball.vy * 84 / 86;
    }
    if (ball.x < 30) {
        ball.vx = -ball.vx;
        ball.ax = -ball.ax;
    } else if (ball.x > 370) {
        ball.vx = -ball.vx;
        ball.ax = -ball.ax;
    }
    ball.ax = ball.ax / 2;
    ball.vx = ball.vx * 185 / 186;
    ball.y = ball.y + ball.vy * deltat + ball.ay * deltat * deltat / 2
    ball.x = ball.x + ball.vx * deltat + ball.ax * deltat * deltat / 2
    ball.vy = ball.vy + ball.ay * deltat
    ball.vx = ball.vx + ball.ax * deltat
    draw_ball(ball);
}

setInterval(compute_position, 40);
<!DOCTYPE html>
<html>
<head><title>Basketball</title></head>
<body>
<canvas id="myCanvas" width="400" height="400" style="border:1px solid #c3c3c3;">
Your browser does not support the canvas element.
</canvas>
</body></html>

答案 6 :(得分:6)

乔希(一会儿)询问浏览器如何知道“绘图过程何时结束”,以避免闪烁。我会直接评论他的帖子,但我的代表不够高。这也只是我的意见。我没有事实来支持它,但我对它有相当的信心,这对将来读这篇文章可能会有所帮助。

我猜测浏览器在完成绘图时并不“知道”。但就像大多数javascript一样,只要你的代码在没有放弃对浏览器的控制的情况下运行,浏览器就会被锁定,并且不会/不能更新/响应它的UI。我猜测如果你清除画布并绘制整个画面而不放弃对浏览器的控制,那么在你完成之前它实际上不会画你的画布。

如果设置渲染跨越多个setTimeout / setInterval / requestAnimationFrame调用的情况,您在一次调用中清除画布并在接下来的几次调用中在画布上绘制元素,每隔5次重复循环(例如)电话,我愿意打赌你会看到闪烁,因为画布会在每次通话后更新。

那说,我不确定我是否相信。我们已经准备好在执行之前将javascript编译成本地机器代码(至少这是Chrome的V8引擎根据我的理解做的)。如果浏览器开始在UI的单独线程中运行javascript并同步对UI元素的任何访问,允许UI在未执行UI的javascript执行期间更新/响应,我不会感到惊讶。当/如果发生这种情况(我知道有很多障碍需要克服,例如当你还在运行其他代码时事件处理程序开始),我们可能会看到没有使用的画布动画上的闪烁某种双缓冲。

就个人而言,我喜欢将两个帆布元素放在彼此顶部并交替显示/绘制在每个框架上的想法。相当不引人注意,可能很容易通过几行代码添加到现有应用程序中。

答案 7 :(得分:5)

网络浏览器没有闪烁!他们已经使用dbl缓冲进行渲染。 Js引擎会在显示之前完成所有渲染。此外,上下文保存和恢复只有堆栈转换矩阵数据等,而不是画布内容本身。 所以,你不需要或不想要dbl缓冲!

答案 8 :(得分:3)

通过使用现有的库创建干净无闪烁的JavaScript动画,您可能会获得最佳里程,而不是自己动手:

这是一个受欢迎的:http://processingjs.org

答案 9 :(得分:2)

Opera 9.10非常慢并显示绘图过程。如果您希望浏览器不使用双缓冲,请尝试使用Opera 9.10。

有人建议浏览器以某种方式确定绘图过程何时结束,但是你能解释一下它是如何工作的吗?我没有注意到Firefox,Chrome或IE9中有任何明显的闪烁,即使绘图很慢,所以看起来这就是他们正在做的事情但是如何实现这对我来说是一个谜。在执行更多绘图指令之前,浏览器将如何知道它正在刷新显示?如果没有执行画布绘制指令,如果间隔超过5ms左右,你认为它们只是计时吗?它假设它可以安全地交换缓冲区吗?

答案 10 :(得分:2)

你需要2个画布:(注意css z-index和position:absolute)

<canvas id="layer1" width="760" height="600" style=" position:absolute; top:0;left:0; 
visibility: visible;  z-index: 0; solid #c3c3c3;">
Your browser does not support the canvas element.
</canvas>
<canvas id="layer2" width="760" height="600" style="position:absolute; top:0;left:0; 
visibility: visible;  z-index: 1; solid #c3c3c3;">
Your browser does not support the canvas element.
</canvas>

你可以注意到第一个画布是可见的,第二个画布隐藏了它在隐藏之后绘制的想法,之后我们将隐藏可见并使隐藏的画布可见。当它隐藏'清晰隐藏的画布

<script type="text/javascript">
var buff=new Array(2);
buff[0]=document.getElementById("layer1");
buff[1]=document.getElementById("layer2");

ctx[0]=buff[0].getContext("2d");
ctx[1]=buff[1].getContext("2d");
var current=0;
// draw the canvas (ctx[ current ]);
buff[1- current ].style.visibility='hidden';
buff[ current ].style.visibility='visible';
ctx[1-current].clearRect(0,0,760,600);
current =1-current;

答案 11 :(得分:2)

在大多数情况下,您不需要这样做,浏览器会为您实现此功能。 但并非总是有用!

当您的绘图非常复杂时,您仍然需要实现此功能。 大多数屏幕更新速率约为60Hz,这意味着屏幕每16ms更新一次。浏览器的更新速率可能接近此数字。如果您的形状需要100毫秒才能完成,您将看到未完成的形状。所以你可以在这种情况下实现双缓冲。

我做了一个测试:Clear a rect, wait for some time, then fill with some color.如果我把时间设置为10毫秒,我就不会看到闪烁。但如果我将其设置为20ms,则会发生闪烁。