Dominando el lienzo HTML5 – Parte 1
Esto comienza una serie de publicaciones de blog sobre el dominio de HTML5 Canvas.
Nos centraremos primero en la animación, ya que es una de las mejores formas de aprovechar el lienzo para aportar nuevos tipos de interacción y visualizaciones a sus aplicaciones web. Este es un adelanto de lo que estamos creando para la parte 1.

Descripción general de Canvas
El lienzo HTML5 proporciona una API de renderizado 2D de modo inmediato que podemos utilizar en nuestras aplicaciones web. No depende de ningún complemento del navegador, solo requiere un navegador lo suficientemente moderno que admita el componente HTML5 Canvas. Sin embargo, incluso si se intenta admitir versiones anteriores de Internet Explorer (8 y anteriores), hay algunos polyfills que se pueden usar para intentar proporcionar compatibilidad con ellos (FlashCanvas, exCanvas, etc.).
Dado que el lienzo es un motor de representación en modo inmediato, en lugar de crear un gráfico de objetos que represente todos los objetos que están visibles, como lo haría en un sistema de modo retenido como Silverlight o WPF, hablaremos con el contexto de representación del lienzo y, para cada fotograma, le diremos qué primitivas visuales se van a representar en qué ubicaciones.
Hello Circle
Para empezar, vamos a renderizar un círculo en la pantalla.
Supondremos que se ha hecho referencia a jQuery para esta aplicación web para ayudarnos a manipular los elementos DOM necesarios de forma sucinta. Pero jQuery ciertamente no es necesario para interactuar con el lienzo. Primero, comenzaremos con el DOM. Necesitamos agregar un elemento canvas en algún lugar de nuestra jerarquía DOM donde queramos mostrar el contenido del lienzo. En este caso, es el único elemento en el cuerpo de mi página.
<canvas id="canvas"></canvas>
He proporcionado un id para que podamos encontrar fácilmente este elemento más adelante cuando queramos representar su contenido.
Ahora es el momento de convertir el círculo en el lienzo. Tenemos que empezar por adquirir el contexto de renderizado 2D del lienzo. El contexto de representación es lo que nos muestra la API que necesitamos para renderizar primitivas en el lienzo.
$(function () {
var canv, context;
$("#canvas").attr("width", "500px").attr("height", "500px");
canv = $("#canvas")[0];
context = canv.getContext("2d");
Aquí estamos usando jQuery para ubicar el lienzo por id, luego configuramos su ancho y alto. Algo que puede confundirte inicialmente es que en realidad hay dos anchos y alturas que son relevantes para el lienzo. El elemento canvas tiene atributos para width y height que representan el tamaño del mapa de bits que se utiliza para representar el contenido dentro del canvas. Mientras tanto, también puede establecer un ancho y alto css en el lienzo. Representan el tamaño al que se escala el mapa de bits generado por el lienzo cuando se muestra en la página. Entonces, por ejemplo, si creas un lienzo de 50 px por 50 px y lo escalas a 500 px por 500 px, se vería muy cuadrado ya que estarías estirando un cuadrado de 50 px para llenar un área cuadrada de 500 px. La mayoría de las veces, probablemente querrás que el tamaño de css sea el mismo que el tamaño del atributo del lienzo.
A continuación, extraemos una referencia al elemento DOM canvas de jQuery y llamamos a getContext("2d") para obtener el contexto de representación. ¿Por qué getContext("2d")? Esto se debe a que el lienzo HTML5 también juega un papel integral en WebGL, más allá de su API 2D, pero por ahora, estamos hablando de 2D.
A continuación, renderizamos el círculo real. Aquí estoy definiendo una función llamada draw que renderizará el círculo y luego la llamo para que haga el renderizado real. ¿Por qué dividí esto? Esto se hará evidente cuando agreguemos animación a la mezcla.
function draw() {
context.fillStyle = "rgb(255,0,0)";
context.beginPath();
context.arc(150, 150, 120, 0, 2 * Math.PI, false);
context.fill();
}
draw();
Aquí estamos:
- Establecer el estilo de relleno del lienzo en rojo. Puede usar la mayoría de la sintaxis que aceptará una propiedad de color css aquí.
- Decirle al contexto que inicie un nuevo camino.
- Dibujar un arco con un centro de 150 px, 150 px con un radio de 120, barriendo de 0 a 360 grados, en el sentido de las agujas del reloj (tenga en cuenta que la API en realidad quiere radianes, no grados)
- Diciéndole al contexto que llene el camino que hemos definido, llenando así el círculo.
Y aquí está el resultado.
Hello Animation
Now, lets animate our circle in some way.
Primero, aquí está la lógica modificada de nuestra versión anterior:
var canv, context, lastTime, duration, progress, forward = true;
$("#canvas").attr("width", "500px").attr("height", "500px");
canv = $("#canvas")[0];
context = canv.getContext("2d");
lastTime = new Date().getTime();
duration = 1000;
progress = 0;
function draw() {
var elapsed, time, b;
time = new Date().getTime();
elapsed = time - lastTime;
lastTime = time;
if (forward) {
progress += elapsed / duration;
} else {
progress -= elapsed / duration;
}
if (progress > 1.0) {
progress = 1.0;
forward = false;
}
if (progress < 0) {
progress = 0;
forward = true;
}
b = 255.0 * progress;
context.fillStyle = "rgb(255," + Math.round(b.toString()) + ",0)";
context.beginPath();
context.arc(150, 150, 120, 0, 2 * Math.PI, false);
context.fill();
}
window.setInterval(draw, 1000 / 60.0);
Entonces, ¿cuáles son las diferencias anteriores?
Primero, estamos usando window.setInterval para llamar a nuestro método draw repetidamente. Estamos intentando hacer esto 60 veces por segundo (1000 milisegundos / 60.0).
window.setInterval(draw, 1000 / 60.0);
A continuación, para cada vez que dibujamos, determinamos cuánto tiempo ha transcurrido desde el último fotograma que se dibujó y actualizamos nuestro progreso hacia la finalización de la animación. Cuando el progreso se completa, invertimos la dirección y lo reducimos a 0. Luego, cuando llegamos a 0 nos damos la vuelta y avanzamos hacia 1 nuevamente. De esta manera, la animación se repite hacia atrás y hacia adelante continuamente.
time = new Date().getTime();
elapsed = time - lastTime;
lastTime = time;
if (forward) {
progress += elapsed / duration;
} else {
progress -= elapsed / duration;
}
if (progress > 1.0) {
progress = 1.0;
forward = false;
}
if (progress < 0) {
progress = 0;
forward = true;
}
A continuación, el progreso se utiliza para impulsar el color que estamos utilizando para renderizar la elipse.
b = 255.0 * progress; context.fillStyle = "rgb(255," + Math.round(b.toString()) + ",0)";
Y así tenemos una elipse que se anima en color.
Mejorando nuestra animación
Podemos hacer un pequeño ajuste a lo anterior con el fin de mejorar la forma en que se realiza la animación. Hay dos suposiciones que la lógica anterior hace que no son necesariamente ciertas. Se supone que la máquina que ejecuta la lógica será lo suficientemente rápida como para proporcionar una animación de 60 fps y que se llamará al método de dibujo con una sincronización lo suficientemente fiable como para crear un efecto animado suave. Ambas suposiciones no son necesariamente ciertas. Es posible que la máquina sea demasiado lenta para realizar la actualización de la animación el número requerido de veces por segundo, y no se garantiza que setInterval se llame de manera confiable en absoluto, especialmente si hay mucha lógica que está martillando la cola de eventos de javascript. Algunos navegadores modernos admiten una API que nos permite hacerlo mejor que esto al realizar animaciones. Básicamente, podemos solicitar que el navegador nos notifique cuando un fotograma de animación está listo, para que podamos responder renderizando algún contenido. Esto proporciona una animación más fiable y fluida, y debería evitar que intentemos animar más fotogramas de los que el sistema es capaz de manejar. Esta es la versión modificada de la lógica que usará un polyfill para usar la API requestAnimationFrame, si está presente, y de lo contrario, recurrirá a nuestro método anterior para animar si estamos en un navegador que no admite requestAnimationFrame.
function ensureQueueFrame() {
if (!window.queueFrame) {
if (window.requestAnimationFrame) {
window.queueFrame = window.requestAnimationFrame;
} else if (window.webkitRequestAnimationFrame) {
window.queueFrame = window.webkitRequestAnimationFrame;
} else if (window.mozRequestAnimationFrame) {
window.queueFrame = window.mozRequestAnimationFrame;
} else {
window.queueFrame = function (callback) {
window.setTimeout(1000.0 / 60.0, callback);
};
}
}
}
$(function () {
var canv, context, lastTime, duration, progress, forward = true;
ensureQueueFrame();
$("#canvas").attr("width", "500px").attr("height", "500px");
canv = $("#canvas")[0];
context = canv.getContext("2d");
lastTime = new Date().getTime();
duration = 1000;
progress = 0;
function draw() {
var ellapsed, time, b;
queueFrame(draw);
time = new Date().getTime();
ellapsed = time - lastTime;
lastTime = time;
if (forward) {
progress += ellapsed / duration;
} else {
progress -= ellapsed / duration;
}
if (progress > 1.0) {
progress = 1.0;
forward = false;
}
if (progress < 0) {
progress = 0;
forward = true;
}
b = 255.0 * progress;
context.fillStyle = "rgb(255," + Math.round(b.toString()) + ",0)";
context.beginPath();
context.arc(150, 150, 120, 0, 2 * Math.PI, false);
context.fill();
}
queueFrame(draw);
});
La próxima vez
La próxima vez nos enfrentaremos a algo mucho más ambicioso. Construiremos una animación llamativa que reaccione al movimiento del ratón sobre el lienzo. También simularemos algunos efectos 3D utilizando renderizado 2D pero transformaciones de matriz 3D.