87

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

4

12 回答 12

82

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

一些代码:

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');
于 2010-05-19T10:09:38.527 回答
41

以下有用的链接除了显示使用双缓冲的示例和优点外,还显示了使用 html5 画布元素的其他几个性能技巧。它包括指向 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);
于 2012-04-27T19:56:50.303 回答
19

我测试过的所有浏览器都会为您处理这种缓冲,方法是在绘制框架的代码完成之前不重新绘制画布。另请参阅 WHATWG 邮件列表: http: //www.mail-archive.com/whatwg@lists.whatwg.org/msg19969.html

于 2011-02-28T02:54:57.380 回答
15

你总是可以做 var canvas2 = document.createElement("canvas"); 而不是将它附加到 DOM 中。

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

于 2016-01-16T12:22:35.093 回答
8

两年多后:

无需“手动”实现双缓冲。Geary 先生在他的《HTML5 Canvas》一书中写到了这一点。

有效减少频闪使用requestAnimationFrame()

于 2013-10-09T16:46:50.047 回答
7

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

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>

于 2012-06-05T06:27:26.370 回答
6

Josh 询问(不久前)浏览器如何知道“绘图过程何时结束”以避免闪烁。我会直接评论他的帖子,但我的代表还不够高。这也只是我的看法。我没有事实来支持它,但我对此相当有信心,它可能对以后阅读本文的其他人有所帮助。

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

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

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

就个人而言,我喜欢两个画布元素相互重叠并交替显示/绘制在每一帧上的想法。相当无干扰,并且可能很容易通过几行代码添加到现有应用程序中。

于 2012-09-25T03:39:32.377 回答
5

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

于 2011-03-06T04:53:17.870 回答
3

与其自己动手,不如使用现有的库来创建干净且无闪烁的 JavaScript 动画,从而获得最佳效果:

这是一个流行的: http: //processingjs.org

于 2010-05-12T20:39:08.167 回答
3

你需要 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;
于 2012-04-14T23:21:57.343 回答
2

Opera 9.10 速度很慢,显示绘图过程。如果您想查看不使用双缓冲的浏览器,请尝试 Opera 9.10。

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

于 2012-01-23T04:25:31.367 回答
2

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

当您的绘图非常复杂时,您仍然必须执行此操作。大部分屏幕更新率在 60Hz 左右,这意味着屏幕每 16ms 更新一次。浏览器的更新率可能接近这个数字。如果您的形状需要 100 毫秒才能完成,您会看到一个未完成的形状。所以你可以在这种情况下实现双缓冲。

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

于 2013-06-16T12:08:10.480 回答