Czy HTML5 / Canvas obsługuje podwójne buforowanie?

83

Chciałbym narysować moją grafikę w buforze, a następnie móc skopiować ją tak, jak jest na płótnie, aby móc tworzyć animacje i unikać migotania. Ale nie mogłem znaleźć tej opcji. Czy ktoś wie, jak mam się do tego zabrać?

Shai UI
źródło
12
Z mojego doświadczenia wynika, że ​​rysowanie na płótnie jest łączone przez przeglądarkę, dzięki czemu animacje są płynne. Czy możesz udostępnić jakiś kod, który migocze podczas opisywania?
powiek
2
Zauważyłem, że IE może migotać w niektórych przypadkach podczas używania explorercanvas, ale to oczywiście nie jest HTML5 i jest to canvaselement tylko emulowany przez VML. Jednak nigdy nie widziałem, aby inne przeglądarki to robiły.
cryo
3
Powiązane ze stackoverflow.com/questions/11777483
julien
2
Naprawdę głupi kod dla początkujących, który nie migocze. jsfiddle.net/linuxlizard/ksohjr4f/3 Zgodnie z wszelkimi prawami, powinno migotać. Przeglądarki są imponujące.
David Poole,
Potrzebujesz tylko podwójnego buforowania, jeśli masz funkcję rysowania asynchronicznego. Dopóki nie poddasz się przeglądarce, tj. Nie zsynchronizujesz rysowania, wszystko będzie dobrze. Jak tylko dodasz obietnicę lub setTimeout lub coś tam, ustępujesz z powrotem przeglądarce i narysuje aktualny stan, zanim zakończy się skutecznie, powodując migotanie.
Albertjan

Odpowiedzi:

38

Poniższy pomocny link, oprócz pokazania przykładów i zalet korzystania z podwójnego buforowania, zawiera kilka innych wskazówek dotyczących wydajności przy korzystaniu z elementu canvas html5. Zawiera łącza do testów jsPerf, które gromadzą wyniki testów z różnych przeglądarek w bazie danych Browserscope. Zapewnia to weryfikację wskazówek dotyczących wydajności.

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

Dla Twojej wygody zamieściłem minimalny przykład skutecznego podwójnego buforowania, jak opisano w artykule.

// 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
źródło
83

Bardzo prostą metodą jest umieszczenie dwóch elementów kanwy w tym samym miejscu na ekranie i ustawienie widoczności dla bufora, który chcesz pokazać. Rysuj na ukrytym i odwróć, gdy skończysz.

Jakiś kod:

CSS:

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

Odwracanie w JS:

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

DrawingBuffer=1-DrawingBuffer;

W tym kodzie tablica „Buffers []” zawiera oba obiekty kanwy. Więc jeśli chcesz zacząć rysować, nadal musisz uzyskać kontekst:

var context = Buffers[DrawingBuffer].getContext('2d');
Fedor van Eldijk
źródło
Poza tematem: Osobiście lubię używać <noscript>i tworzyć moje elementy płótna w JavaScript. Zazwyczaj i tak chciałbyś sprawdzić obsługę kanwy w JavaScript, więc dlaczego miałbyś chcieć umieścić zastępczą wiadomość kanwy w kodzie HTML?
Shea
19

Wszystkie przeglądarki, które przetestowałem, obsługują to buforowanie, nie malując płótna, dopóki kod, który rysuje twoją ramkę, nie zostanie ukończony. Zobacz także listę mailingową WHATWG: http://www.mail-archive.com/[email protected]/msg19969.html

Edward Coffey
źródło
10
Cóż, zauważam migotanie lub pęknięcie ekranu. Nie wiem, jak to opisać. Używam najnowszej przeglądarki Chrome w systemie Linux.
grom
14

Zawsze możesz to zrobić var canvas2 = document.createElement("canvas"); i w ogóle nie dodawać go do DOM.

Samo powiedzenie, ponieważ display:none; wydajecie się mieć obsesję na jego punkcie , wydaje mi się czystsze i naśladuje ideę podwójnego buforowania o wiele dokładniej niż po prostu posiadanie niezręcznie niewidocznego płótna.

DeadlyBacon
źródło
8

Ponad dwa lata później:

Nie ma potrzeby „ręcznego” wdrażania podwójnego buforowania. Pan Geary napisał o tym w swojej książce „HTML5 Canvas” .

Aby skutecznie zredukować migotanie requestAnimationFrame()!

ohager
źródło
1
Jak wyjaśnisz poprawę wydajności, którą zaobserwowano przy użyciu podwójnego buforowania? html5rocks.com/en/tutorials/canvas/performance
Rick Suggs
@ricksuggs Cóż, „podwójne buforowanie” lub „renderowanie poza ekranem” wspomniane w html5rocks jest trochę inne niż myślałem. Uważałem DB za zamianę stron ekranowych (w VRAM), która w rzeczywistości była tylko operacją wskaźnika zamiast kopiowania fragmentów pamięci. OP poprosił o użycie DB, aby uniknąć migotania, do czego naprawdę odnosi się requestAnimationFrame (). Może ten artykuł o renderowaniu poza ekranem może być interesujący. Tam odpowiadam na pytanie OP dotyczące kopiowania i wklejania buforowanych danych obrazu na ekran za pomocą bufora Sprite
ohager
6

Josh zapytał (jakiś czas temu), skąd przeglądarka wie, „kiedy kończy się proces rysowania”, aby uniknąć migotania. Skomentowałbym bezpośrednio do jego postu, ale mój przedstawiciel nie jest wystarczająco wysoki. To tylko moja opinia. Nie mam faktów na poparcie tego, ale czuję się dość pewnie, co do tego i może to być pomocne dla innych, którzy przeczytają to w przyszłości.

Domyślam się, że przeglądarka nie „wie”, kiedy skończysz rysować. Ale tak jak większość javascript, o ile twój kod działa bez zrzekania się kontroli na przeglądarkę, przeglądarka jest zasadniczo zablokowana i nie może / nie może zaktualizować / odpowiedzieć na swój interfejs użytkownika. Zgaduję, że jeśli wyczyścisz płótno i narysujesz całą ramkę bez zrzekania się kontroli na przeglądarkę, tak naprawdę nie będzie rysować płótna, dopóki nie skończysz.

Jeśli skonfigurujesz sytuację, w której renderowanie obejmuje wiele wywołań setTimeout / setInterval / requestAnimationFrame, w których wyczyścisz kanwę w jednym wywołaniu i narysujesz elementy na płótnie w następnych kilku wywołaniach, powtarzając cykl (na przykład) co 5 wywołań, ja Byłbym skłonny założyć się, że zobaczysz migotanie, ponieważ płótno będzie aktualizowane po każdym wezwaniu.

To powiedziawszy, nie jestem pewien, czy temu ufam. Jesteśmy już w punkcie, w którym javascript jest kompilowany do natywnego kodu maszynowego przed wykonaniem (przynajmniej tak robi silnik V8 Chrome z tego, co rozumiem). Nie zdziwiłbym się, gdyby nie minęło dużo czasu, zanim przeglądarki zaczęły uruchamiać swój javascript w oddzielnym wątku z interfejsu użytkownika i synchronizować dostęp do elementów interfejsu użytkownika, umożliwiając interfejsowi aktualizację / odpowiedź podczas wykonywania javascript, który nie miał dostępu do interfejsu użytkownika. Kiedy / jeśli tak się stanie (i rozumiem, że istnieje wiele przeszkód, które trzeba by pokonać, na przykład programy obsługi zdarzeń uruchamiające się, gdy nadal używasz innego kodu), prawdopodobnie zobaczymy migotanie animacji płótna, które nie używają jakiś rodzaj podwójnego buforowania.

Osobiście podoba mi się pomysł dwóch elementów płótna umieszczonych jeden na drugim i naprzemiennie pokazanego / narysowanego na każdej klatce. Dość dyskretny i prawdopodobnie dość łatwy do dodania do istniejącej aplikacji za pomocą kilku wierszy kodu.

Zawietrzny
źródło
Dokładnie. Zobacz przykład JSFiddle tutaj stackoverflow.com/questions/11777483/… .
Eric
6

Dla niewierzących, oto jakiś migający kod. Zauważ, że wyraźnie usuwam poprzednie koło.

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>

Jan
źródło
4
Wydaje mi się, że nie migocze, przynajmniej nie wtedy, gdy kula nie porusza się o więcej niż promień w każdej klatce. Chrome / Windows.
Jules,
10
Dodałem wywołanie requestAnimFrame - patrz tutaj - do twojego przykładu na jsfiddle.net/GzSWJ/28 - już nie
miga
5

W przeglądarkach internetowych nie ma migotania! Używają już buforowania dbl do renderowania. Silnik js wykona cały twój rendering przed jego wyświetleniem. Ponadto kontekstowe zapisywanie i przywracanie tylko danych macierzy transformacji stosu i tym podobnych, a nie samej zawartości kanwy. Więc nie potrzebujesz ani nie chcesz buforowania dbl!

Luka
źródło
8
Czy możesz przedstawić dowody na poparcie swoich roszczeń?
Rick Suggs
@ricksuggs Uważam, że nie używanie DB powoduje złe szarpanie w animacjach, jeszcze nie próbowałem DB
FutuToad
3

Zamiast tworzyć własną, prawdopodobnie uzyskasz najlepszy przebieg, korzystając z istniejącej biblioteki do tworzenia czystej i wolnej od migotania animacji JavaScript:

Oto popularny: http://processingjs.org

a7drew
źródło
95
Dokładnie! Po co zawracać sobie głowę i pisać 10 wierszy własnego kodu, skoro możesz po prostu użyć całej biblioteki 275 KB, nie mając o tym najmniejszego pojęcia !? Tak, byłem sarkastyczny.
Tom
3

potrzebujesz 2 płótna: (zwróć uwagę na indeks z css i położenie: bezwzględne)

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

możesz zauważyć, że pierwsze płótno jest widoczne, a drugie jest ukryte. Pomysł polega na narysowaniu na ukrytym, po czym ukryjemy to, co jest widoczne, a ukryte płótno będzie widoczne. kiedy jest ukryty, wyczyść ukryte płótno

<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
źródło
Niezła implementacja, na wypadek, gdyby ktoś musiał ręcznie zastosować technikę podwójnego buforowania. Po prostu dodaj następujący wiersz: var ctx = new Array (2); do pustego wiersza w powyższym kodzie.
dimmat
2

Opera 9.10 jest bardzo powolna i pokazuje proces rysowania. Jeśli chcesz, aby przeglądarka nie używała podwójnego buforowania, wypróbuj Opera 9.10.

Niektórzy sugerowali, że przeglądarki w jakiś sposób określają, kiedy kończy się proces rysowania, ale czy możesz wyjaśnić, jak to działa? Nie zauważyłem żadnego wyraźnego migotania w Firefoksie, Chrome lub IE9, nawet gdy rysowanie jest powolne, więc wydaje się, że to właśnie robią, ale jak to się robi, jest dla mnie tajemnicą. Skąd przeglądarka może kiedykolwiek wiedzieć, że odświeża ekran tuż przed wykonaniem kolejnych instrukcji rysowania? Czy myślisz, że po prostu mierzą czas, więc jeśli interwał dłuższy niż 5 ms minie bez wykonywania instrukcji rysowania płótna, zakłada, że ​​może bezpiecznie zamienić bufory?

Josh
źródło
2

W większości sytuacji nie musisz tego robić, przeglądarka implementuje to za Ciebie. Ale nie zawsze przydatne!

Nadal musisz to zaimplementować, gdy rysunek jest bardzo skomplikowany. Większość częstotliwości odświeżania ekranu wynosi około 60 Hz, co oznacza aktualizację ekranu co 16 ms. Szybkość aktualizacji przeglądarki może zbliżać się do tej liczby. Jeśli Twój kształt wymaga 100 ms do ukończenia, zobaczysz nieukończony kształt. Możesz więc w takiej sytuacji zaimplementować podwójne buforowanie.

Wykonałem test: Clear a rect, wait for some time, then fill with some color.jeśli ustawię czas na 10 ms, nie zobaczę migotania. Ale jeśli ustawię to na 20 ms, pojawi się migotanie.

Chien-Wei Huang
źródło