La API Canvas ofrece una de las más poderosas características de HTML5. Permite a desarrolladores trabajar con un medio visual e interactivo para proveer capacidades de aplicaciones de escritorio para la web.
Al comienzo del libro hablamos sobre cómo HTML5 está reemplazando previos complementos o plug-ins, como Flash o Applets Java, por ejemplo. Había dos cosas importantes a considerar para independizar a la web de tecnologías desarrolladas por terceros: procesamiento de video y aplicaciones gráficas. El elemento < video > y la API para medios cubren el primer aspecto muy bien, pero no hacen nada acerca de los gráficos.
La API Canvas se hace cargo del aspecto gráfico y lo hace de una forma extremadamente efectiva. Canvas nos permite dibujar, presentar gráficos en pantalla, animar y procesar imágenes y texto, y trabaja junto con el resto de la especificación para crear aplicaciones completas e incluso videojuegos en 2 y 3 dimensiones para la web.
El elemento <canvas>
Este elemento genera un espacio rectangular vacío en la página web (lienzo) en el cual serán mostrados los resultados de ejecutar los métodos provistos por la API. Cuando es creado, produce sólo un espacio en blanco, como un elemento < div > vacío, pero con un propósito totalmente diferente.
<!DOCTYPE html> <html lang="es"> <head> <title>Canvas API</title> <script src="canvas.js"></script> </head> <body> <section id="cajalienzo"> <canvas id="lienzo" width="500" height="300"> Su navegador no soporta el elemento canvas </canvas> </section> </body> </html>
Solo es necesario especificar unos pocos atributos para este elemento, como puede ver en el Listado 7-1. Los atributos width (ancho) y height (alto) declaran el tamaño del lienzo en píxeles. Estos atributos son necesarios debido a que todo lo que sea dibujado sobre el elemento tendrá esos valores como referencia. Al atributo id, como en otros casos, nos facilita el acceso al elemento desde el código JavaScript.
Eso es básicamente todo lo que el elemento < canvas > hace. Simplemente crea una caja vacía en la pantalla. Es solo a través de JavaScript y los nuevos métodos y propiedades introducidos por la API que esta superficie se transforma en algo práctico.
getContext()
El método getContext() es el primer método que tenemos que llamar para dejar al elemento < canvas > listo para trabajar. Genera un contexto de dibujo que será asignado al lienzo. A través de la referencia que retorna podremos aplicar el resto de la API.
function iniciar(){ var elemento=document.getElementById('lienzo'); lienzo=elemento.getContext('2d'); } window.addEventListener("load", iniciar, false);
En el Listado 7-2, una referencia al elemento < canvas > fue almacenada en la variable elemento y el contexto de dibujo fue creado por getContext(‘2d’). El método puede tomar dos valores: 2d y 3d. Esto es, por supuesto, para ambientes de 2 dimensiones y 3 dimensiones. Por el momento solo el contexto 2d está disponible, pero serios esfuerzos están siendo volcados en el desarrollo de una API estable en 3 dimensiones.
El contexto de dibujo del lienzo será una grilla de píxeles listados en filas y columnas de arriba a abajo e izquierda a derecha, con su origen (el píxel 0,0) ubicado en la esquina superior izquierda del lienzo.
Dibujando en el lienzo
Luego de que el elemento < canvas > y su contexto han sido inicializados podemos finalmente comenzar a crear y manipular gráficos. La lista de herramientas provista por la API para este propósito es extensa, desde la creación de simples formas y métodos de dibujo hasta texto, sombras o transformaciones complejas. Vamos a estudiarlas una por una.
Dibujando rectángulos
Normalmente el desarrollador deberá preparar la figura a ser dibujada en el contexto (como veremos pronto), pero existen algunos métodos que nos permiten dibujar directamente en el lienzo, sin preparación previa. Estos métodos son específicos para formas rectangulares y son los únicos que generan una forma primitiva (para obtener otras formas tendremos que combinar otras técnicas de dibujo y trazados complejos). Los métodos disponibles son los siguientes:
- fillRect(x, y, ancho, alto) Este método dibuja un rectángulo sólido. La esquina superior izquierda será ubicada en la posición especificada por los atributos x e y. Los atributos ancho y alto declaran el tamaño
- strokeRect(x, y, ancho, alto) Similar al método anterior, éste dibujará un rectángulo vacío (solo su contorno)
- clearRect(x, y, ancho, alto) Esta método es usado para substraer píxeles del área especificada por sus atributos. Es un borrador rectangular.
function iniciar(){ var elemento=document.getElementById('lienzo'); lienzo=elemento.getContext('2d'); lienzo.strokeRect(100,100,120,120); lienzo.fillRect(110,110,100,100); lienzo.clearRect(120,120,80,80); } window.addEventListener("load", iniciar, false);
Esta es la misma función del Listado 7-2, pero incorpora los nuevos métodos estudiados para dibujar una figura en el lienzo. Como puede ver, el contexto fue asignado a la variable global lienzo, y ahora esta variable es usada para referenciar el contexto en cada método.
El primer método usado en la función, strokeRect(100,100,120,120), dibuja un rectángulo vacío con la esquina superior izquierda en la posición 100,100 y un tamaño de 120 píxeles. (Este es un cuadrado de 120 píxeles). El segundo método, fillRect(110,110, 100,100), dibuja un rectángulo sólido, esta vez comenzando desde la posición 110,110 del lienzo. Y finalmente, con el último método, clearRect(120,120,80,80), un recuadro de 80 pixeles es substraído del centro de la figura.
API Canvas
La Figura 7-1 es solo una representación de lo que verá en la pantalla luego de ejecutar el código del Listado 7-3. El elemento < canvas > es como una grilla, con su origen en la esquina superior izquierda y el tamaño especificado en sus atributos.
Los rectángulos son dibujados en el lienzo en la posición declarada por los atributos x e y, y uno sobre el otro de acuerdo al orden en el código. (El primero en aparecer en el código será dibujado primero, el segundo será dibujado por encima del anterior, y así sucesivamente). Existe un método para personalizar cómo las figuras son dibujadas en pantalla, pero lo veremos más adelante.
Colores
Hasta el momento hemos usado el color otorgado por defecto, negro sólido, pero podemos especificar el color que queremos aplicar mediante sintaxis CSS utilizando las siguientes propiedades:
- “strokeStyle”. Esta propiedad declara el color para el contorno de la figura
- “illStyle”. Esta propiedad declara el color para el interior de la figura
- “globalAlpha”. Esta propiedad no es para definir color sino transparencia. Especifica la transparencia para todas las figuras dibujadas en el lienzo.
function iniciar(){ var elemento=document.getElementById('lienzo'); lienzo=elemento.getContext('2d'); lienzo.fillStyle="#000099"; lienzo.strokeStyle="#990000"; lienzo.strokeRect(100,100,120,120); lienzo.fillRect(110,110,100,100); lienzo.clearRect(120,120,80,80); } window.addEventListener("load", iniciar, false);
Los colores en el Listado 7-4 fueron declarados usando números hexadecimales. Podemos también usar funciones como rgb() o incluso especificar transparencia para la figura aprovechando la función rgba(). Estos métodos deben ser siempre escritos entre comillas (por ejemplo, strokeStyle=”rgba(255,165,0,1)”).
Cuando un nuevo color es especificado se vuelve el color por defecto para el resto de los dibujos, a menos que volvamos a cambiarlo más adelante.
A pesar de que el uso de la función rgba() es posible, existe otra propiedad más específica para declarar el nivel de transparencia: globalAlpha. Su sintaxis es globalAlpha=valor, donde valor es un número entre 0.0 (totalmente opaco) y 1.0 (totalmente transparente).
Gradientes
Gradientes son una herramienta esencial en cualquier programa de dibujo estos días, y esta API no es la excepción. Así como en CSS3, los gradientes en la API Canvas pueden ser lineales o radiales, y pueden incluir puntos de terminación para combinar colores.
- createLinearGradient(x1, y1, x2, y2). Este método crea un objeto que luego será usado para aplicar un gradiente lineal al lienzo
- createRadialGradient(x1, y1, r1, x2, y2, r2). Este método crea un objeto que luego será usado para aplicar un gradiente circular o radial al lienzo usando dos círculos. Los valores representan la posición del centro de cada círculo y sus radios
- addColorStop(posición, color). Este método especifica los colores a ser usados por el gradiente. El atributo posición es un valor entre 0.0 y 1.0 que determina dónde la degradación comenzará para ese color en particular.
function iniciar(){ var elemento=document.getElementById('lienzo'); lienzo=elemento.getContext('2d'); var gradiente=lienzo.createLinearGradient(0,0,10,100); gradiente.addColorStop(0.5, '#0000FF'); gradiente.addColorStop(1, '#000000'); lienzo.fillStyle=gradiente; lienzo.fillRect(10,10,100,100); lienzo.fillRect(150,10,200,100); } window.addEventListener("load", iniciar, false);
En el Listado 7-5, creamos el objeto gradiente desde la posición 0,0 a la 10,100, otorgando una leve inclinación hacia la izquierda. Los colores fueron declarados por el método addColorStop() y el gradiente logrado fue finalmente aplicado a la propiedad fillStyle, como un color regular.
Note que las posiciones del gradiente son correspondientes al lienzo, no a las figuras que queremos afectar. El resultado es que si movemos los rectángulos dibujados al final de la función hacia una nueva posición, el gradiente para esos triángulos cambiará.
Creando trazados
Los métodos estudiados hasta el momento dibujan directamente en el lienzo, pero ese no es siempre el caso. Normalmente tendremos que procesar figuras en segundo plano y una vez que el trabajo esté hecho enviar el resultado al contexto para que sea dibujado. Con este propósito, API Canvas introduce varios métodos con los que podremos generar trazados.
Un trazado es como un mapa a ser seguido por el lápiz. Una vez declarado, el trazado será enviado al contexto y dibujado de forma permanente en el lienzo. El trazado puede incluir diferentes tipos de líneas, como líneas rectas, arcos, rectángulos, entre otros, para crear figuras complejas.
Existen dos métodos para comenzar y cerrar el trazado:
- beginPath(). Este método comienza la descripción de una nueva figura. Es llamado en primer lugar, antes de comenzar a crear el trazado
- closePath(). Este método cierra el trazado generando una línea recta desde el último punto hasta el punto de origen. Puede ser ignorado cuando utilizamos el método fill() para dibujar el trazado en el lienzo.
También contamos con tres métodos para dibujar el trazado en el lienzo:
- stroke(). Este método dibuja el trazado como una figura vacía (solo el contorno)
- fill(). Este método dibuja el trazado como una figura sólida. Cuando usamos este método no necesitamos cerrar el trazado con closePath(), el trazado es automáticamente cerrado con una línea recta trazada desde el punto final hasta el origen
- clip(). Este método declara una nueva área de corte para el contexto. Cuando el contexto es inicializado, el área de corte es el área completa ocupada por el lienzo. El método clip() cambiará el área de corte a una nueva forma creando de este modo una máscara. Todo lo que caiga fuera de esa máscara no será dibujado.
function iniciar(){ var elemento=document.getElementById('lienzo'); lienzo=elemento.getContext('2d'); lienzo.beginPath(); // aquí va el trazado lienzo.stroke(); } window.addEventListener("load", iniciar, false);
El código del Listado 7-6 no crea absolutamente nada, solo incorpora los métodos necesarios para iniciar y luego dibujar el trazado en pantalla.
Para crear el trazado y la figura real que será enviada al contexto y dibujada en el lienzo, contamos con varios métodos disponibles:
- moveTo(x, y). Este método mueve el lápiz a una posición específica para continuar con el trazado. Nos permite comenzar o continuar el trazado desde diferentes puntos, evitando líneas continuas
- lineTo(x, y). Este método genera una línea recta desde la posición actual del lápiz hasta la nueva declarada por los atributos x e y
- rect(x, y, ancho, alto). Este método genera un rectángulo. A diferencia de los métodos estudiados anteriormente, éste generará un rectángulo que formará parte del trazado (no directamente dibujado en el lienzo). Los atributos tienen la misma función
- arc(x, y, radio, ángulo inicio, ángulo final, dirección). Este método genera un arco o un círculo en la posición x e y, con un radio y desde un ángulo declarado por sus atributos. El último valor es un valor booleano (falso o verdadero) para indicar la dirección a favor o en contra de las agujas del reloj
- quadraticCurveTo(cpx, cpy, x, y). Este método genera una curva Bézier cuadrática desde la posición actual del lápiz hasta la posición declarada por los atributos x e y. Los atributos cpx y cpy indican el punto que dará forma a la curva
- bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y). Este método es similar al anterior pero agrega dos atributos más para generar una curva Bézier cúbica. Ahora disponemos de dos puntos para moldear la curva, declarados por los atributos cp1x, cp1y, cp2x y cp2y.
Veamos un trazado sencillo para entender cómo funcionan:
function iniciar(){ var elemento=document.getElementById('lienzo'); lienzo=elemento.getContext('2d'); lienzo.beginPath(); lienzo.moveTo(100,100); lienzo.lineTo(200,200); lienzo.lineTo(100,200); lienzo.stroke(); } window.addEventListener("load", iniciar, false);
Recomendamos siempre establecer la posición inicial del lápiz inmediatamente después de iniciar el trazado con beginPath(). En el código del Listado 7-7 el primer paso fue mover el lápiz a la posición 100,100 y luego generar una línea desde ese punto hasta el punto 200,200. Ahora la posición del lápiz es 200,200 y la siguiente línea será generada desde aquí hasta el punto 100,200. Finalmente, el trazado es dibujado en el lienzo como una forma vacía con el método stroke().
Si prueba el código en su navegador, verá un triángulo abierto en la pantalla. Este triángulo puede ser cerrado o incluso rellenado y transformado en una figura sólida usando diferentes métodos, como vemos en el siguiente ejemplo:
function iniciar(){ var elemento=document.getElementById('lienzo'); lienzo=elemento.getContext('2d'); lienzo.beginPath(); lienzo.moveTo(100,100); lienzo.lineTo(200,200); lienzo.lineTo(100,200); lienzo.closePath(); lienzo.stroke(); } window.addEventListener("load", iniciar, false);
El método closePath() simplemente agrega una línea recta al trazado, desde el último al primer punto, cerrando la figura. Por su lado, usando el método stroke() al final de nuestro trazado dibujamos un triángulo vacío en el lienzo.
Para lograr una figura sólida, este método debe ser reemplazado por fill():
function iniciar(){ var elemento=document.getElementById('lienzo'); lienzo=elemento.getContext('2d'); lienzo.beginPath(); lienzo.moveTo(100,100); lienzo.lineTo(200,200); lienzo.lineTo(100,200); lienzo.fill(); } window.addEventListener("load", iniciar, false);
Ahora la figura en la pantalla será un triángulo sólido. El método fill() cierra el trazado automáticamente, por lo que ya no tenemos que usar closePath() para lograrlo.
Uno de los métodos mencionados anteriormente para dibujar un trazado en el lienzo fue clip(). Este método en realidad no dibuja nada, lo que hace es crear una máscara con la forma del trazado para seleccionar qué será dibujado y qué no. Todo lo que caiga fuera de la máscara no se dibujará en el lienzo. Veamos un ejemplo:
function iniciar(){ var elemento=document.getElementById('lienzo'); lienzo=elemento.getContext('2d'); lienzo.beginPath(); lienzo.moveTo(100,100); lienzo.lineTo(200,200); lienzo.lineTo(100,200); lienzo.clip(); lienzo.beginPath(); for(f=0; f<300; f=f+10){ lienzo.moveTo(0,f); lienzo.lineTo(500,f); } lienzo.stroke(); } window.addEventListener("load", iniciar, false);
Para mostrar exactamente cómo funciona el método clip(), en el Listado 7-10 utilizamos un bucle for para crear líneas horizontales cada 10 píxeles. Estas líneas van desde el lado izquierdo al lado derecho del lienzo, pero solo las partes de las líneas que caen dentro de la máscara (el triángulo) serán dibujadas.
Ahora que ya sabemos cómo dibujar trazados, es tiempo de ver el resto de las alternativas con las que contamos para crearlos. Hasta el momento hemos estudiado cómo generar líneas rectas y formas rectangulares. Para figuras circulares, la API provee tres métodos: arc(), quadraticCurveTo() y bezierCurveTo(). El primero es relativamente sencillo y puede generar círculos parciales o completos, como mostramos en el siguiente ejemplo:
function iniciar(){ var elemento=document.getElementById('lienzo'); lienzo=elemento.getContext('2d'); lienzo.beginPath(); lienzo.arc(100,100,50,0,Math.PI*2, false); lienzo.stroke(); } window.addEventListener("load", iniciar, false);
Utilización del valor PI
Lo primero que seguramente notará en el método arc() en nuestro ejemplo es el uso del valor PI. Este método usa radianes en lugar de grados para los valores del ángulo. En radianes, el valor PI representa 180 grados, por lo que la formula PI*2 multiplica PI por 2 obteniendo un ángulo de 360 grados.
El código en el Listado 7-11 genera un arco con centro en el punto 100,100 y un radio de 50 pixeles, comenzando a 0 grados y terminando a Math.PI*2 grados, lo que representa un círculo completo. El uso de la propiedad PI del objeto Math nos permite obtener el valor preciso de PI.
Si necesitamos calcular el valor en radianes de cualquier ángulo en grados usamos la fórmula: Math.PI / 180 × grados, como en el próximo ejemplo:
function iniciar(){ var elemento=document.getElementById('lienzo'); lienzo=elemento.getContext('2d'); lienzo.beginPath(); var radianes=Math.PI/180*45; lienzo.arc(100,100,50,0,radianes, false); lienzo.stroke(); } window.addEventListener("load", iniciar, false);
Con el código del Listado 7-12 obtenemos un arco que cubre 45 grados de un círculo. Intente cambiar el valor de la dirección a true (verdadero). En este caso, el arco será generado desde 0 grados a 315, creando un círculo abierto.
Una cosa importante a considerar es que si continuamos construyendo el trazado luego del arco, el actual punto de comienzo será el final del arco. Si no deseamos que esto pase tendremos que usar el método moveTo() para cambiar la posición del lápiz, como hicimos anteriormente.
Dependiendo de las figuras…
Sin embargo, si la próxima figura es otro arco (por ejemplo, un círculo completo) siempre recuerde que el método moveTo() mueve el lápiz virtual hacia el punto en el cual el círculo comenzará a ser dibujado, no el centro del círculo. Digamos que el centro del círculo que queremos dibujar se encuentra en el punto 300,150 y su radio es de 50. El método moveTo() debería mover el lápiz a la posición 350,150 para comenzar a dibujar el círculo.
Además de arc(), existen dos métodos más para dibujar curvas, en este caso curvas complejas. El método quadraticCurveTo() genera una curva Bézier cuadrática, y el método bezierCurveTo() es para curvas Bézier cúbicas. La diferencia entre estos dos métodos es que el primero cuenta con un solo punto de control y el segundo con dos, creando de este modo diferentes tipos de curvas.
function iniciar(){ var elemento=document.getElementById('lienzo'); lienzo=elemento.getContext('2d'); lienzo.beginPath(); lienzo.moveTo(50,50); lienzo.quadraticCurveTo(100,125, 50,200); lienzo.moveTo(250,50); lienzo.bezierCurveTo(200,125, 300,125, 250,200); lienzo.stroke(); } window.addEventListener("load", iniciar, false);
Para la curva cuadrática movimos el lápiz virtual a la posición 50,50 y finalizamos la curva en el punto 50,200. El punto de control para esta curva fue ubicado en la posición 100,125.
La curva generada por el método bezierCurveTo() es un poco más compleja. Hay dos puntos de control para esta curva, el primero en la posición 200,125 y el segundo en la posición 300,125.
Los valores en la Figura 7-2 indican los puntos de control para las curvas. Moviendo estos puntos cambiamos la forma de la curva.