【问题标题】:Does HTML5/Canvas Support Double Buffering?HTML5/Canvas 是否支持双缓冲?
【发布时间】:2011-02-17 05:24:40
【问题描述】:

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

【问题讨论】:

  • 我的经验是画布绘图是由浏览器合并的,因此动画很流畅。你能分享一些你描述的闪烁的代码吗?
  • 我注意到 IE 在某些情况下使用 explorercanvas 时会闪烁,但这当然不是 HTML5 并且是仅由 VML 模拟的 canvas 元素。不过,我从未见过任何其他浏览器这样做。
  • 真正愚蠢的初学者代码,不会闪烁。 jsfiddle.net/linuxlizard/ksohjr4f/3版权所有,应该闪烁。浏览器令人印象深刻。
  • 如果你有异步绘图功能,你只需要双缓冲。只要您不屈服于浏览器,即使绘图同步,您就可以了。一旦你在其中添加了一个 Promise 或 setTimeout 或其他东西,你就会返回给浏览器,它会在它完成之前绘制当前状态,从而有效地导致闪烁。

标签: javascript html canvas double-buffering


【解决方案1】:

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

一些代码:

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 中放置画布后备消息?
【解决方案2】:

以下有用的链接除了显示使用双缓冲的示例和优点外,还显示了使用 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);

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 嗯,我注意到闪烁或屏幕撕裂。不知道怎么形容。在 Linux 上使用最新的 Chrome。
    【解决方案4】:

    你总是可以做到的 var canvas2 = document.createElement("canvas"); 并且根本不将其附加到 DOM。

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

    【讨论】:

      【解决方案5】:

      两年多以后:

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

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

      【讨论】:

      • 您如何解释使用双缓冲所见证的性能改进? html5rocks.com/en/tutorials/canvas/performance
      • @ricksuggs 好吧,html5rocks 上提到的“双缓冲”或“屏幕外渲染”与我想象的有点不同。我将 DB 视为交换屏幕页面(在 vram 中),这实际上只是一个指针操作,而不是复制内存块。 OP 要求使用 DB 来避免闪烁,这实际上是由 requestAnimationFrame() 解决的。也许这个关于离屏渲染的article 可能很有趣。在那里,我回答了 OP 关于使用 Sprite 缓冲区将缓冲的图像数据复制和粘贴到屏幕的问题
      【解决方案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>

      【讨论】:

      • 对我来说似乎没有闪烁,至少当球每帧移动的距离不超过其半径时。 Chrome/Windows。
      • 我已经添加了对 requestAnimFrame 的调用 - 参见 here - 到您在 jsfiddle.net/GzSWJ/28 的示例 - 它不再闪烁
      【解决方案7】:

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

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

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

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

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

      【讨论】:

      【解决方案8】:

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

      【讨论】:

      • 你能提供一些证据来支持你的说法吗?
      • @ricksuggs 我发现不使用 DB 会导致动画出现严重的抖动,我还没有尝试过 DB
      【解决方案9】:

      您可能会通过使用现有库来创建干净且无闪烁的 JavaScript 动画,而不是自己滚动,从而获得最佳效果:

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

      【讨论】:

      • 没错!当您可以使用整个 275KB 的库而对它毫无头绪时,为什么还要费心编写 10 行自己的代码!?是的,我是在讽刺。
      【解决方案10】:

      你需要 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;
      

      【讨论】:

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

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

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

      【讨论】:

        【解决方案12】:

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

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-07-31
          • 2011-06-28
          • 2012-08-28
          • 2012-11-02
          • 2018-09-22
          • 2012-04-12
          相关资源
          最近更新 更多