Unterstützt HTML5 / Canvas Double Buffering?

82

Ich möchte meine Grafiken in einen Puffer zeichnen und sie dann so wie sie sind auf die Leinwand kopieren, damit ich Animationen erstellen und Flackern vermeiden kann. Aber ich konnte diese Option nicht finden. Weiß jemand, wie ich das machen kann?

foreyez
quelle
12
Ich habe die Erfahrung gemacht, dass das Zeichnen auf Leinwand vom Browser zusammengeführt wird, damit die Animationen reibungslos sind. Können Sie einen Code freigeben, der wie beschrieben flackert?
Augenlidlosigkeit
2
Ich habe festgestellt, dass der IE bei der Verwendung in einigen Fällen flackern kann explorercanvas, aber das ist natürlich nicht HTML5 und ist ein canvasElement, das lediglich von VML emuliert wird. Ich habe noch nie einen anderen Browser gesehen.
Kryo
2
Wirklich dummer Anfängercode, der nicht flackert. jsfiddle.net/linuxlizard/ksohjr4f/3 Sollte auf jeden Fall flackern. Browser sind beeindruckend.
David Poole
Sie benötigen nur eine doppelte Pufferung, wenn Sie über eine asynchrone Zeichenfunktion verfügen. Solange Sie dem Browser nicht nachgeben, dh das Zeichnen synchronisieren, ist alles in Ordnung. Sobald Sie dort ein Versprechen oder setTimeout oder etwas hinzufügen, geben Sie dem Browser nach und er zeichnet den aktuellen Status, bevor er effektiv beendet ist, was zu Flimmern führt.
Albertjan

Antworten:

38

Der folgende hilfreiche Link zeigt nicht nur Beispiele und Vorteile der doppelten Pufferung, sondern auch einige andere Leistungstipps für die Verwendung des HTML5-Canvas-Elements. Es enthält Links zu jsPerf-Tests, mit denen Testergebnisse über mehrere Browser hinweg in einer Browserscope-Datenbank zusammengefasst werden. Dies stellt sicher, dass die Leistungstipps überprüft werden.

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

Der Einfachheit halber habe ich ein minimales Beispiel für eine effektive Doppelpufferung beigefügt, wie im Artikel beschrieben.

// 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);
Rick Suggs
quelle
83

Eine sehr einfache Methode besteht darin, zwei Canvas-Elemente an derselben Bildschirmposition zu haben und die Sichtbarkeit für den Puffer festzulegen, den Sie anzeigen möchten. Zeichne auf das Versteckte und drehe es um, wenn du fertig bist.

Etwas Code:

CSS:

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

JS einblättern:

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

DrawingBuffer=1-DrawingBuffer;

In diesem Code enthält das Array 'Buffers []' beide Canvas-Objekte. Wenn Sie also mit dem Zeichnen beginnen möchten, müssen Sie immer noch den Kontext abrufen:

var context = Buffers[DrawingBuffer].getContext('2d');
Fedor van Eldijk
quelle
Off Topic: Ich persönlich verwende <noscript>und erstelle meine Canvas-Elemente gerne in Javascript. Normalerweise möchten Sie sowieso in Javascript nach Canvas-Unterstützung suchen. Warum sollten Sie also eine Canvas-Fallback-Nachricht in Ihren HTML-Code einfügen?
Shea
19

Alle von mir getesteten Browser übernehmen diese Pufferung für Sie, indem sie die Zeichenfläche erst neu streichen, wenn der Code, der Ihren Frame zeichnet, vollständig ist. Siehe auch die WHATWG-Mailingliste: http://www.mail-archive.com/[email protected]/msg19969.html

Edward Coffey
quelle
10
Nun, ich bemerke ein Flackern oder einen Bildschirmriss. Ich bin mir nicht sicher, wie ich es beschreiben soll. Verwenden des neuesten Chrome unter Linux.
Grom
14

Sie können es jederzeit tun var canvas2 = document.createElement("canvas"); und überhaupt nicht an das DOM anhängen.

Nur zu sagen, da ihr so ​​besessen display:none; davon zu sein scheint, scheint mir sauberer zu sein und ahmt die Idee der doppelten Pufferung genauer nach als nur eine unbeholfen unsichtbare Leinwand.

DeadlyBacon
quelle
8

Mehr als zwei Jahre später:

Es ist nicht erforderlich, die doppelte Pufferung manuell zu implementieren. Herr Geary schrieb darüber in seinem Buch "HTML5 Canvas" .

Flimmergebrauch effektiv reduzieren requestAnimationFrame()!

ohager
quelle
1
Wie erklären Sie die Leistungsverbesserungen, die durch die Verwendung von Doppelpufferung festgestellt wurden? html5rocks.com/de/tutorials/canvas/performance
Rick Suggs
@ricksuggs Nun, die auf html5rocks erwähnte "doppelte Pufferung" oder "Offscreen-Wiedergabe" ist etwas anders als ich dachte. Ich betrachtete DB als Austausch von Bildschirmseiten (in vram), was praktisch nur eine Zeigeroperation war, anstatt Speicherblöcke zu kopieren. Das OP hat darum gebeten, DB zu verwenden, um ein Flackern zu vermeiden, was von requestAnimationFrame () wirklich behoben wird. Vielleicht ist dieser Artikel über Offscreen-Rendering interessant. Dort beantworte ich die Frage des OP zum Kopieren und Einfügen gepufferter Bilddaten auf den Bildschirm mit einem Sprite-Puffer
ohager
6

Josh fragte (vor einiger Zeit), wie der Browser weiß, "wann der Zeichenvorgang endet", um ein Flackern zu vermeiden. Ich hätte seinen Beitrag direkt kommentiert, aber mein Repräsentant ist nicht hoch genug. Auch das ist nur meine Meinung. Ich habe keine Fakten, um dies zu belegen, aber ich bin ziemlich zuversichtlich, und es kann für andere hilfreich sein, dies in Zukunft zu lesen.

Ich vermute, der Browser "weiß" nicht, wann Sie mit dem Zeichnen fertig sind. Aber genau wie bei den meisten Javascript ist der Browser im Wesentlichen gesperrt und kann / kann nicht auf seine Benutzeroberfläche aktualisieren / reagieren, solange Ihr Code ausgeführt wird, ohne die Kontrolle an den Browser abzugeben. Ich vermute, wenn Sie die Leinwand löschen und Ihren gesamten Rahmen zeichnen, ohne die Kontrolle an den Browser abzugeben, wird Ihre Leinwand erst dann gezeichnet, wenn Sie fertig sind.

Wenn Sie eine Situation einrichten, in der Ihr Rendering mehrere setTimeout / setInterval / requestAnimationFrame-Aufrufe umfasst, in der Sie die Zeichenfläche in einem Aufruf löschen und in den nächsten mehreren Aufrufen Elemente auf Ihrer Zeichenfläche zeichnen und den Zyklus (z. B.) alle 5 Aufrufe wiederholen, I. Ich würde wetten, dass Sie ein Flackern sehen würden, da die Leinwand nach jedem Anruf aktualisiert wird.

Trotzdem bin ich mir nicht sicher, ob ich dem vertrauen würde. Wir sind bereits an dem Punkt angelangt, an dem Javascript vor der Ausführung zu nativem Maschinencode kompiliert wird (zumindest macht das die V8-Engine von Chrome, soweit ich weiß). Es würde mich nicht wundern, wenn es nicht zu lange dauern würde, bis Browser ihr Javascript in einem von der Benutzeroberfläche getrennten Thread ausführen und den Zugriff auf Benutzeroberflächenelemente synchronisieren, sodass die Benutzeroberfläche während der Ausführung von Javascript, die nicht auf die Benutzeroberfläche zugreift, aktualisiert / reagiert werden kann. Wenn / falls dies passiert (und ich verstehe, dass es viele Hürden gibt, die überwunden werden müssten, z. B. Event-Handler, die starten, während Sie noch anderen Code ausführen), werden wir wahrscheinlich Flimmern auf Leinwand-Animationen sehen, die nicht verwendet werden eine Art Doppelpufferung.

Persönlich mag ich die Idee, dass zwei Leinwandelemente übereinander positioniert und abwechselnd auf jedem Rahmen gezeigt / gezeichnet werden. Ziemlich unauffällig und wahrscheinlich ziemlich einfach zu einer vorhandenen Anwendung mit ein paar Codezeilen hinzuzufügen.

Lee
quelle
Genau. Ein Beispiel finden Sie in der JSFiddle hier stackoverflow.com/questions/11777483/… .
Eric
6

Für die Ungläubigen gibt es hier einen flackernden Code. Beachten Sie, dass ich explizit lösche, um den vorherigen Kreis zu löschen.

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>

John
quelle
4
Scheint für mich nicht zu flackern, zumindest nicht, wenn sich der Ball nicht um mehr als seinen Radius pro Frame bewegt. Chrome / Windows.
Jules
10
Ich habe einen Aufruf von requestAnimFrame hinzugefügt - siehe hier - zu Ihrem Beispiel unter jsfiddle.net/GzSWJ/28 - es flackert nicht mehr
rhu
5

In Webbrowsern flackert es nicht! Sie verwenden bereits dbl-Pufferung für ihr Rendern. Js Engine erstellt Ihr gesamtes Rendering, bevor es angezeigt wird. Außerdem können im Kontext nur Stapeltransformationsmatrixdaten und dergleichen gespeichert und wiederhergestellt werden, nicht der Canvas-Inhalt selbst. Sie brauchen oder wollen also keine Doppelpufferung!

Luka
quelle
8
Können Sie Beweise für Ihre Behauptungen vorlegen?
Rick Suggs
@ Ricksuggs Ich finde, dass die Nichtverwendung von DB zu einem schlechten Ruckeln in Animationen führt. Ich habe DB noch nicht
ausprobiert
3

Anstatt Ihre eigenen zu rollen, werden Sie wahrscheinlich die beste Kilometerleistung erzielen, indem Sie eine vorhandene Bibliothek zum Erstellen einer sauberen und flimmerfreien JavaScript-Animation verwenden:

Hier ist eine beliebte: http://processingjs.org

a7 zog
quelle
94
Genau! Warum sollten Sie sich die Mühe machen und 10 Zeilen Ihres eigenen Codes schreiben, wenn Sie nur eine ganze 275-KB-Bibliothek verwenden können, ohne die geringste Ahnung davon zu haben? Ja, ich war sarkastisch.
Tom
10
Ja, hart viel?
Davidtbernal
3

Sie benötigen 2 Zeichenfläche: (Beachten Sie den CSS-Z-Index und die Position: absolut)

<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>

Sie können feststellen, dass die erste Leinwand sichtbar und die zweite versteckt ist. Die Idee ist, auf die versteckte Leinwand zu zeichnen. Danach werden wir die sichtbare verstecken und die versteckte Leinwand sichtbar machen. wenn es versteckt ist 'klare versteckte Leinwand

<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;
Aviad Gispan
quelle
Gute Implementierung für den Fall, dass jemand die Doppelpuffertechnik manuell anwenden muss. Fügen Sie einfach die folgende Zeile hinzu: var ctx = new Array (2); auf die leere Zeile in Ihrem Code oben.
Dimmat
2

Opera 9.10 ist sehr langsam und zeigt den Zeichenvorgang. Wenn Sie möchten, dass ein Browser keine doppelte Pufferung verwendet, probieren Sie Opera 9.10 aus.

Einige Leute schlugen vor, dass Browser irgendwie bestimmen, wann der Zeichenprozess endet, aber können Sie erklären, wie das funktionieren kann? Ich habe in Firefox, Chrome oder IE9 kein offensichtliches Flackern bemerkt, selbst wenn die Zeichnung langsam ist. Es scheint also, dass sie das tun, aber wie dies erreicht wird, ist mir ein Rätsel. Wie würde der Browser jemals wissen, dass die Anzeige aktualisiert wird, bevor weitere Zeichenanweisungen ausgeführt werden sollen? Denken Sie, dass sie es nur zeitlich festlegen, wenn ein Intervall von mehr als 5 ms vergeht, ohne dass eine Zeichenanweisung für die Leinwand ausgeführt wird, wird davon ausgegangen, dass Puffer sicher ausgetauscht werden können?

Josh
quelle
2

In den meisten Situationen müssen Sie dies nicht tun, der Browser implementiert dies für Sie. Aber nicht immer nützlich!

Sie müssen dies noch implementieren, wenn Ihre Zeichnung sehr kompliziert ist. Der größte Teil der Bildschirmaktualisierungsrate liegt bei etwa 60 Hz, dh die Bildschirmaktualisierungen erfolgen pro 16 ms. Die Aktualisierungsrate des Browsers kann sich dieser Zahl annähern. Wenn Ihre Form 100 ms benötigt, um fertiggestellt zu werden, sehen Sie eine unvollständige Form. In dieser Situation können Sie also eine doppelte Pufferung implementieren.

Ich habe einen Test gemacht: Clear a rect, wait for some time, then fill with some color.Wenn ich die Zeit auf 10 ms einstelle, sehe ich kein Flackern. Aber wenn ich es auf 20 ms stelle, passiert das Flackern.

Chien-Wei Huang
quelle