代码之家  ›  专栏  ›  技术社区  ›  Shai UI

HTML5/Canvas是否支持双缓冲?

  •  72
  • Shai UI  · 技术社区  · 14 年前

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

    12 回复  |  直到 9 年前
        1
  •  26
  •   Roko C. Buljan    10 年前

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

    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
  •  81
  •   quantumpotato    9 年前

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

    一些代码:

    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');
    
        3
  •  18
  •   Edward Coffey    13 年前

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

        4
  •  11
  •   DeadlyBacon    9 年前

    你可以一直这样做 var canvas2 = document.createElement("canvas"); 并且根本不将其附加到DOM。

    只是说你们好像很迷恋 display:none; 在我看来,它更干净,更准确地模仿了双重缓冲的想法,而不是仅仅拥有一张笨拙的隐形画布。

        5
  •  7
  •   ohager    11 年前

    两年多后:

    不需要“手动”实现双缓冲。盖里先生在他的书中写过这个 "HTML5 Canvas" .

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

        6
  •  6
  •   Rob W jminkler    12 年前

    对于不信的人,这里有一些闪烁的密码。请注意,我正在清除上一个圆。

    http://coderextreme.net/basketball2.html ( http://jsfiddle.net/GzSWJ/ )

    <!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>
    <script>
    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);
    </script></body></html>
    
        7
  •  6
  •   Lee    12 年前

    乔希问(不久前)浏览器如何知道“绘图过程何时结束”,以避免闪烁。我本可以直接对他的职位发表评论,但我的代表还不够高。这也是我的观点。我没有事实支持它,但我对它相当有信心,它可能会有助于其他人今后阅读这篇文章。

    我猜浏览器不知道你什么时候画完。但是就像大多数javascript一样,只要代码运行时不放弃对浏览器的控制,浏览器就基本上被锁定了,不会/不能更新/响应其用户界面。我猜,如果你清除画布并在不放弃对浏览器的控制的情况下绘制整个框架,那么在你完成之前,它不会真正绘制你的画布。

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

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

    就我个人而言,我喜欢将两个画布元素放置在彼此上方,并交替显示/绘制在每一帧上的想法。相当不具侵入性,可能很容易用几行代码添加到现有的应用程序中。

        8
  •  5
  •   Luka    13 年前

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

        9
  •  3
  •   a7drew    14 年前

    与其自己滚动,不如使用现有的库来创建干净、无闪烁的javascript动画,从而获得最好的效果:

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

        10
  •  2
  •   Josh    13 年前

    歌剧9.10非常缓慢,展示了绘画过程。如果您希望看到浏览器不使用双缓冲,请尝试Opera 9.10 out。

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

        11
  •  2
  •   Aviad Gispan    12 年前

    您需要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;
    
        12
  •  2
  •   Chien-Wei Huang    11 年前

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

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

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