¿HTML5 / Canvas admite el almacenamiento en búfer doble?

83

Lo que me gustaría hacer es dibujar mis gráficos en un búfer y luego poder copiarlos como están en el lienzo para poder hacer la animación y evitar el parpadeo. Pero no pude encontrar esta opción. ¿Alguien sabe cómo puedo hacer esto?

Interfaz de usuario de Shai
fuente
12
Mi experiencia ha sido que el navegador fusiona el dibujo en lienzo para que las animaciones sean fluidas. ¿Puede compartir algún código que parpadee como lo describe?
párpados
2
He notado que IE puede parpadear en algunos casos cuando se usa explorercanvas, pero eso, por supuesto, no es HTML5 y es un canvaselemento simplemente emulado por VML. Sin embargo, nunca he visto a ningún otro navegador hacerlo.
crio
3
Relacionado con stackoverflow.com/questions/11777483
julien
2
Código para principiantes realmente tonto que no parpadea. jsfiddle.net/linuxlizard/ksohjr4f/3 Por todos los derechos, debería parpadear. Los navegadores son impresionantes.
David Poole
Solo necesita almacenamiento en búfer doble si tiene una función de dibujo asíncrono. Mientras no ceda al navegador, es decir, haga que el dibujo sea sincrónico, estará bien. Tan pronto como agregue una promesa o setTimeout o algo allí, cede el paso al navegador y dibujará el estado actual antes de que termine causando parpadeo.
albertjan

Respuestas:

38

El siguiente enlace útil, además de mostrar ejemplos y ventajas del uso de búfer doble, muestra varios otros consejos de rendimiento para usar el elemento canvas html5. Incluye enlaces a las pruebas jsPerf, que agregan los resultados de las pruebas a través de los navegadores en una base de datos de Browserscope. Esto asegura que se verifiquen las sugerencias de rendimiento.

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

Para su conveniencia, he incluido un ejemplo mínimo de almacenamiento en búfer doble eficaz como se describe en el artículo.

// 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
fuente
83

Un método muy simple es tener dos elementos de lienzo en la misma ubicación de la pantalla y establecer la visibilidad para el búfer que necesita mostrar. Dibuja lo oculto y voltea cuando hayas terminado.

Algún código:

CSS:

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

Volteando en JS:

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

DrawingBuffer=1-DrawingBuffer;

En este código, la matriz 'Buffers []' contiene ambos objetos canvas. Entonces, cuando quiera comenzar a dibujar, aún necesita obtener el contexto:

var context = Buffers[DrawingBuffer].getContext('2d');
Fedor van Eldijk
fuente
Fuera de tema: personalmente me gusta usar <noscript>y crear mis elementos de lienzo en Javascript. Por lo general, querrá verificar la compatibilidad con el lienzo en Javascript de todos modos, entonces, ¿por qué querría poner un mensaje de respaldo del lienzo en su HTML?
Shea
19

Todos los navegadores que he probado manejan este almacenamiento en búfer por usted al no volver a pintar el lienzo hasta que el código que dibuja su marco se haya completado. Consulte también la lista de correo de WHATWG: http://www.mail-archive.com/[email protected]/msg19969.html

Edward Coffey
fuente
10
Bueno, noto un parpadeo o un desgarro en la pantalla. No estoy seguro de como describirlo. Utilizando la última versión de Chrome en Linux.
grom
14

Siempre puede hacerlo var canvas2 = document.createElement("canvas"); y no agregarlo al DOM en absoluto.

Solo digo que, dado que ustedes parecen tan obsesionados con display:none; él, me parece más limpio e imita la idea de doble búfer de manera más precisa que solo tener un lienzo extrañamente invisible.

Tocino mortal
fuente
8

Más de dos años después:

No es necesario implementar "manualmente" el almacenamiento en búfer doble. El Sr. Geary escribió sobre esto en su libro "HTML5 Canvas" .

¡Para reducir eficazmente el uso del parpadeo requestAnimationFrame()!

ohager
fuente
1
¿Cómo explica las mejoras de rendimiento observadas mediante el uso de búfer doble? html5rocks.com/en/tutorials/canvas/performance
Rick Suggs
@ricksuggs Bueno, el "doble búfer" o "renderizado fuera de la pantalla" mencionado en html5rocks es un poco diferente de lo que pensaba. Consideré DB como intercambio de páginas de pantalla (en vram), que efectivamente era solo una operación de puntero en lugar de copiar fragmentos de memoria. El OP pidió usar DB para evitar el parpadeo, lo que realmente se aborda mediante requestAnimationFrame (). Quizás este artículo sobre Renderizado fuera de pantalla sea interesante. Allí respondo la pregunta del OP sobre copiar y pegar datos de imagen almacenados en búfer en la pantalla usando un búfer de Sprite
ohager
6

Josh preguntó (hace un tiempo) cómo sabe el navegador "cuándo termina el proceso de dibujo" para evitar el parpadeo. Habría comentado directamente en su publicación, pero mi reputación no es lo suficientemente alta. Además, esta es solo mi opinión. No tengo datos que lo respalden, pero me siento bastante seguro y puede ser útil para otras personas que lean esto en el futuro.

Supongo que el navegador no "sabe" cuando terminas de dibujar. Pero al igual que la mayoría de los javascript, siempre que su código se ejecute sin ceder el control al navegador, el navegador está esencialmente bloqueado y no podrá / no podrá actualizar / responder a su interfaz de usuario. Supongo que si borra el lienzo y dibuja todo el marco sin ceder el control al navegador, en realidad no dibujará su lienzo hasta que haya terminado.

Si configura una situación en la que su representación abarca múltiples llamadas setTimeout / setInterval / requestAnimationFrame, donde borra el lienzo en una llamada y dibuja elementos en su lienzo en las siguientes llamadas, repitiendo el ciclo (por ejemplo) cada 5 llamadas, yo Estaría dispuesto a apostar a que verá un parpadeo, ya que el lienzo se actualizará después de cada llamada.

Dicho esto, no estoy seguro de que pueda confiar en eso. Ya estamos en el punto en que javascript se compila en código de máquina nativo antes de la ejecución (al menos eso es lo que hace el motor V8 de Chrome, según tengo entendido). No me sorprendería si no pasara mucho tiempo antes de que los navegadores comenzaran a ejecutar su javascript en un hilo separado de la interfaz de usuario y sincronizaran cualquier acceso a los elementos de la interfaz de usuario, lo que permite que la interfaz de usuario se actualice / responda durante la ejecución de javascript que no estaba accediendo a la interfaz de usuario. Cuando o si eso sucede (y entiendo que hay muchos obstáculos que tendrían que superarse, como que los controladores de eventos se inicien mientras todavía está ejecutando otro código), probablemente veremos parpadeo en la animación del lienzo que no está usando una especie de doble búfer.

Personalmente, me encanta la idea de dos elementos de lienzo colocados uno encima del otro y alternando que se muestran / dibujan en cada marco. Bastante poco intrusivo y probablemente se puede agregar fácilmente a una aplicación existente con unas pocas líneas de código.

Sotavento
fuente
Exactamente. Vea JSFiddle aquí stackoverflow.com/questions/11777483/… para ver un ejemplo.
Eric
6

Para los incrédulos, aquí hay un código parpadeante. Tenga en cuenta que estoy despejando explícitamente para borrar el círculo anterior.

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>

Juan
fuente
4
No parece parpadear para mí, al menos no cuando la bola no se mueve más de su radio en cada cuadro. Chrome / Windows.
Jules
10
Agregué una llamada a requestAnimFrame - vea aquí - a su ejemplo en jsfiddle.net/GzSWJ/28 - ya no parpadea
rhu
5

¡No hay parpadeo en los navegadores web! Ya utilizan el almacenamiento en búfer dbl para su renderizado. El motor Js hará todo su renderizado antes de mostrarlo. Además, guardar y restaurar el contexto solo apila datos de matriz de transformación y demás, no el contenido del lienzo en sí. Por lo tanto, no necesita ni desea el almacenamiento en búfer dbl.

Luka
fuente
8
¿Puede proporcionar alguna evidencia para respaldar sus afirmaciones?
Rick Suggs
@ricksuggs Encuentro que no usar DB da como resultado una mala sacudida en las animaciones, todavía no he probado DB
FutuToad
3

En lugar de rodar el suyo, probablemente obtendrá el mejor rendimiento utilizando una biblioteca existente para crear animaciones JavaScript limpias y sin parpadeos:

Aquí hay uno popular: http://processingjs.org

a7drew
fuente
94
¡Exactamente! ¿Por qué molestarse y escribir 10 líneas de su propio código, cuando puede usar una biblioteca completa de 275 KB sin tener la menor idea al respecto? Sí, estaba siendo sarcástico.
Tom
10
Sí, ¿mucho?
davidtbernal
3

necesita 2 lienzos: (observe el índice z css y la posición: absoluta)

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

puede notar que el primer lienzo es visible y el segundo está oculto, la idea es dibujar sobre el oculto, luego ocultaremos el visible y haremos visible el lienzo oculto. cuando está oculto, claro lienzo oculto

<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
fuente
Buena implementación, en caso de que alguien necesite aplicar manualmente la técnica de doble búfer. Simplemente agregue la siguiente línea: var ctx = new Array (2); a la línea vacía en su código anterior.
dimmat
2

Opera 9.10 es muy lento y muestra el proceso de dibujo. Si desea que un navegador no utilice doble búfer, pruebe Opera 9.10.

Algunas personas sugirieron que los navegadores determinan de alguna manera cuándo termina el proceso de dibujo, pero ¿puede explicar cómo puede funcionar? No he notado ningún parpadeo obvio en Firefox, Chrome o IE9, incluso cuando el dibujo es lento, por lo que parece que eso es lo que están haciendo, pero para mí es un misterio cómo se logra. ¿Cómo sabría el navegador que está actualizando la pantalla justo antes de que se ejecuten más instrucciones de dibujo? ¿Crees que simplemente lo cronometran, de modo que si pasa un intervalo de más de 5 ms sin ejecutar una instrucción de dibujo de lienzo, se supone que puede intercambiar búferes de manera segura?

Josh
fuente
2

En la mayoría de las situaciones, no necesita hacer esto, el navegador lo implementa por usted. ¡Pero no siempre es útil!

Aún debe implementar esto cuando su dibujo sea muy complicado. La mayor parte de la frecuencia de actualización de la pantalla es de aproximadamente 60 Hz, lo que significa que la pantalla se actualiza cada 16 ms. La tasa de actualización del navegador puede acercarse a este número. Si su forma necesita 100 ms para completarse, verá una forma incompleta. Entonces puede implementar el doble búfer en esta situación.

Hice una prueba: Clear a rect, wait for some time, then fill with some color.si configuro el tiempo en 10 ms, no veré parpadeo. Pero si lo configuro en 20 ms, el parpadeo ocurre.

Chien-Wei Huang
fuente