HTML5 / Canvas是否支持双缓冲?


83

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


12
我的经验是,浏览器会合并画布绘图,以使动画流畅。您能否共享一些您描述时闪烁的代码?
失眼

2
我注意到使用时IE在某些情况下可能会闪烁explorercanvas,但这当然不是HTML5,它canvas只是由VML模拟的元素。我从未见过其他浏览器可以做到这一点。
cryo 2010年


2
真正愚蠢的初学者代码,不会闪烁。 jsfiddle.net/linuxlizard/ksohjr4f/3 按所有权利,应闪烁。浏览器令人印象深刻。
大卫·普尔

如果您具有异步绘制功能,则仅需要双缓冲。只要您不屈服于浏览器,即使绘图同步,就可以了。一旦在其中添加了promise或setTimeout或其他内容,您就会让回到浏览器,并且它将在完成状态之前绘制当前状态,从而有效地引起闪烁。
albertjan

Answers:


38

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

http://www.html5rocks.com/zh-CN/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);

83

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

一些代码:

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 []”包含两个画布对象。因此,当您要开始绘制时,仍然需要获取上下文:

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

主题外:我个人喜欢使用<noscript>,并用Javascript创建画布元素。通常,无论如何,您通常都希望在Javascript中检查画布支持,那么为什么要在HTML中放入画布后备消息呢?
2012年


14

您可以始终这样做, var canvas2 = document.createElement("canvas"); 而根本不将其附加到DOM。

只是说,既然你们似乎display:none; 对它如此着迷,对我来说似乎更干净,并且比仅仅具有笨拙的看不见的画布更精确地模仿了双重缓冲的想法。


8

两年多以后:

不需要“手动”实现双重缓冲。Geary先生在他的《 HTML5 Canvas》一书中写道。

为了有效减少闪烁使用requestAnimationFrame()


1
您如何解释使用双缓冲已见证的性能改进?html5rocks.com/en/tutorials/canvas/performance
Rick Suggs

@ricksuggs好吧,html5rocks上提到的“双缓冲”或“屏幕外渲染”与我所想的有些不同。我认为DB是交换屏幕页面(在vram中),实际上这只是一个指针操作,而不是复制内存块。OP要求使用DB来避免闪烁,这实际上是由requestAnimationFrame()解决的。也许有关离屏渲染的这篇文章可能很有趣。在这里,我回答了OP的有关使用Sprite Buffer将缓冲的图像数据复制和粘贴到屏幕的问题
ohager 2014年

6

约什(前一段时间)询问浏览器如何知道“绘制过程何时结束”,以避免闪烁。我会直接对他的帖子发表评论,但我的代表不够高。这也是我的看法。我没有事实要备份,但是我对此很有信心,这对将来阅读此书的人可能会有帮助。

我猜您完成绘制后浏览器不会“知道”。但是,就像大多数JavaScript一样,只要您的代码在不放弃对浏览器的控制的情况下运行,浏览器实际上就被锁定了,并且不会/无法更新/响应其UI。我猜想,如果您清除画布并绘制整个框架而没有放弃对浏览器的控制,则直到完成后它才真正绘制您的画布。

如果您设置了一个呈现跨多个setTimeout / setInterval / requestAnimationFrame调用的情况,您在一个调用中清除了画布,并在接下来的几个调用中绘制了画布上的元素,例如每5个调用重复一次循环,我们愿意打赌您会看到闪烁,因为在每次调用后都会更新画布。

也就是说,我不确定我是否会相信。我们已经到了在执行之前将javascript编译成本机代码的程度(至少从我的理解来看,这就是Chrome的V8引擎所做的事情)。如果不久之后浏览器就开始在与UI分开的线程中运行javascript并同步对UI元素的任何访问,从而允许UI在不访问UI的javascript执行期间进行更新/响应,那么我不会感到惊讶。当/如果发生这种情况(并且我知道有许多障碍需要克服,例如在您仍在运行其他代码时启动事件处理程序),我们可能会在画布动画上看到没有使用的闪烁某种双重缓冲。

就个人而言,我喜欢两个画布元素彼此重叠放置并交替显示在每个框架上的想法。相当简单,只需几行代码就可以很容易地将其添加到现有应用程序中。


究竟。看到这里的jsfiddle stackoverflow.com/questions/11777483/...的一个例子。
艾瑞克(Eric)

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>


4
对于我来说似乎并不闪烁,至少在球每帧移动的半径不超过其半径时不会闪烁。Chrome / Windows。
Jules

10
我在jsfiddle.net/GzSWJ/28的示例中添加了对requestAnimFrame的调用-参见此处-它不再闪烁了
rhu

5

Web浏览器中没有闪烁!他们已经使用dbl缓冲进行渲染。Js引擎将在显示之前进行所有渲染。同样,上下文保存和还原仅堆栈转换矩阵数据等,而不是画布内容本身。因此,您不需要或不需要dbl缓冲!


8
您能否提供一些证据来支持您的主张?
瑞克·萨格斯

@ricksuggs我发现不使用DB会导致动画
混乱

3

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

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


94
究竟!当您只需要使用整个275KB的库而没有丝毫线索的时候,为什么还要打扰并编写10行代码呢?是的,我很讽刺。
汤姆(Tom)2010年

10
太厉害了吗?
davidtbernal 2011年

3

您需要2个canvas :(请注意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;

很好的实现,以防有人需要手动应用双重缓冲技术。只需添加以下行:var ctx = new Array(2); 到上面代码中的空行。
dimmat

2

Opera 9.10非常慢,并且显示了绘制过程。如果要查看浏览器不使用双重缓冲,请尝试使用Opera 9.10。

有人建议浏览器以某种方式确定绘图过程的结束时间,但是您能解释一下它如何工作吗?即使绘图速度很慢,我也没有注意到Firefox,Chrome或IE9中出现任何明显的闪烁,因此看来这是他们的工作,但如何完成对我来说还是个谜。浏览器将如何知道即将执行更多绘图指令之前正在刷新显示?您是否认为他们只是对它计时,如果在不执行画布绘制指令的情况下经过了大约5毫秒左右的间隔,并假设它可以安全地交换缓冲区?


2

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

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

我进行了测试:Clear a rect, wait for some time, then fill with some color.如果将时间设置为10ms,则不会出现闪烁。但是,如果将其设置为20ms,则会发生闪烁。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.