Reto HTML5: Jugando con el Canvas

Dentro del estándar HTML5 uno de los componentes más atractivos del elemento canvas.

Este elemento nos ofrece una superficie de dibujo a la cual podemos aplicar una serie de funciones de dibujado sencillas y muy potentes. (https://www.w3.org/TR/html5/the-canvas-element.html).

Para ilustrar este tema vamos a escribir un poco de software.

¿Qué necesito para empezar?

clip_image001

WebMatrix. La nueva herramienta de desarrollo de Microsoft que incluye todo lo necesario para el desarrollo de sitios web. Desarrollar sitios web con HTML5 nunca ha sido tan fácil. Descargalo ya.

clip_image002

Internet Explorer 9. Si no tienes instalado Internet Explorer 9 deberías probarlos. Puedes descargarlo desde aquí .

clip_image003

Visual Studio. Si ya tienes VS instalado perfecto, pero por si acaso, aquí tienes el enlace de descarga de la versión gratuita express o de la Ultimate Trial.

clip_image004

Web Standards Update for Microsoft Visual Studio 2010 SP1. Con esta extensión de Visual Studio tendrás acceso funcionalidades de soporte de HTML5 en el entorno de desarrollo. Haz clic aquí para descargarla.

Si necesitas ayuda o tienes algún problema con cualquiera de los pasos de instalación o del reto, puedes añadir un comentario a este post, twitearnos un mensaje a @esmsdn o simplemente twitear con el hashtag #retosmsdn.

Empecemos por el principio

Lo primero es construir una página Web HMTL5 y añadir un elemento canvas.

<!DOCTYPE html />
<html >
<head>
<meta charset="utf-8" />
<title>Jugando con el canvas</title>
</head>
<body>
<canvas id="DrawingArea">
Tu navegador no soporta el elemento canvas.
</canvas>
</body>
</html>

Para verificar el soporte de canvas del navegador antes de usarlo en JavaScript vamos a usar modernizr, en este caso modernizr-2.0.6.js que descargamos desde https://www.modernizr.com/

Modernizr hace muchas cosas pero para no añadir más componentes lo vamos a utilizar sólo para usar la detección de capacidades.

...

<head>
<meta charset="utf-8" />
<title>Jugando con el canvas</title>
<script src="modernizr-2.0.6.js"></script>
<script type="text/javascript">
window.addEventListener('load', WindowLoad, true);

function WindowLoad()
{
// Comprobamos si tenemos canvas....
if (!Modernizr.canvas) {
return;
}
// seguimos a lo nuesto
}
</script>
</head>

Si todo va bien vamos a recuperar el objeto detrás del elemento

            // seguimos a lo nuesto
var areaDeDibujo; // objeto que representa el canvas
var contexto; // contexto de dibujado para el canvas.

// obtenemos el canvas
areaDeDibujo = document.getElementById("AreaDeDibujo");

// obtenemos un texto de dibujo en 2 dimensiones para el canvas
contexto = areaDeDibujo.getContext("2d");

Además obtenemos un contexto de dibujado sobre el que aplicar las primitivas de dibujo que queremos.

Usando las primitivas de dibujo

La lista de las primitivas de dibujo la podemos encontrar en https://msdn.microsoft.com/es-es/library/ff975057.aspx

Lo cierto es que es bastante sencillo utilizar esas primitivas:

En este caso vamos a pintar una imagen preexistente creada con un programa de dibujo como Expression Design

clip_image005

Una imagen de 4x4 píxeles a la que llamamos PuntoAmarillo.png y que agregamos al directorio de nuestra página web.

Hay varias formas en las que podemos cargar esta imagen para utilizarla. Bien definiendo una etiqueta img.

<img src="PuntoAmarillo.png" id="laImagen" />

Y buscando la imagen entre los controles del DOM

var imagen = document.getElementById("laImagen");

O Bien mediante la creación del objeto en JavaScript.

var imagen = new Image();
imagen.src = "PuntoAmarillo.png";

A partir de esta imagen, lo único que necesitamos para dibujarla es invocar drawImage sobre el contexto.

contexto.drawImage(imagen, x, y);

En este caso usamos la lista más corta de parámetros de drawImage.

En https://msdn.microsoft.com/es-es/library/ff975414.aspx encontraremos la lista completa de parámetros y un buen ejemplo de cómo utilizarlos para extraer un parte de una imagen.

Como parte del reto, vamos a simular el comportamiento de objetos con una cierta propiedad, llamémosla masa, que se atraen unos a otros según una cierta ley de proporcionalidad entre las masas y de proporcionalidad inversa al cuadrado de las distancias… ¿os suena? https://es.wikipedia.org/wiki/Gravedad

Encapsulamos el comportamiento de estos objetos en una clase tal que:

function Estrella(masa, xPos, yPos, xVel, yVel) {

    this.masa = masa;

    this.xPos = xPos;

    this.yPos = yPos;

    this.xVel = xVel;

    this.yVel = yVel;

 

    this.Actualizar = function (xCampo, yCampo, gMasa) {

        this.xPos += this.xVel;

        this.yPos += this.yVel;

 

        // El campo sin la masa de este objeto tiene como centro de masas

        var nMasa = gMasa - masa;

        var xC = (xCampo - (this.xPos * masa)) / nMasa;

        var yC = (yCampo - (this.yPos * masa)) / nMasa;

 

        // distancia entre el centro de masas y el objeto

        var d = Math.sqrt(((xC - this.xPos) * (xC - this.xPos)) + ((yC - this.yPos) * (yC - this.yPos)));

 

        // vector unitario desde el objeto al centro de masa

        var u = { x: (xC - this.xPos) / d, y: (yC - this.yPos) / d };

 

        // parte real de la acelareación...

        var parteReal = G * (nMasa * this.masa) / d * d;

 

        // aplicada sobre el vector de dirección modificando el vector velocidad

        this.xVel += parteReal * u.x;

        this.yVel += parteReal * u.y;

 

    }

 

    this.Dibujar = function (contexto, imagen) {

        // renormalizamos las coordenadas

        // el centro del canvas es (0,0)

        // y las ordenasdas (eje y) crecen hacia arriba

        var x = this.xPos + (contexto.canvas.width / 2);

        var y = (contexto.canvas.height / 2) - this.yPos;

        // pintamos la imagen que nos den... dónde nos toca

        contexto.drawImage(imagen, x, y);

    }

}

 

Solo nos queda pensar en el bucle de la animación…

 function DibujarEscena() {
  
             // Borramos la escena
             contexto.fillStyle = '#000000';
             contexto.fillRect(0, 0, areaDeDibujo.width, areaDeDibujo.height);
  
             // Calculamos el campo
             gMasa = 0;
             xCampo = 0;
             yCampo = 0;
             for (i = 0; i < estrellas.length; i++) {
                 gMasa += estrellas[i].masa;
                 xCampo += estrellas[i].xPos * estrellas[i].masa;
                 yCampo += estrellas[i].yPos * estrellas[i].masa;
             }
             // actualizamos la posición de los objetos
             for (i = 0; i < estrellas.length; i++) {
                 estrellas[i].Actualizar(xCampo, yCampo, gMasa);
             }
  
             // Dibujamos los objetos
             for (i = 0; i < estrellas.length; i++) {
                 estrellas[i].Dibujar(contexto, imagen);
             }
         }

Para poner en marcha el dibujado podemos usar setInterval (https://msdn.microsoft.com/es-es/ms536749):

         window.setInterval(DibujarEscena, 20);

Poniendo orden en todo el código nos queda:

 <!DOCTYPE html />
 <html>
 <head>
     <meta charset="utf-8" />
     <title>Jugando con el canvas</title>
     <script src="modernizr-2.0.6.js"></script>
     <script src="Estrella.js" type="text/javascript"></script>
     <script type="text/javascript">
         window.addEventListener('load', WindowLoad, true);
  
         var areaDeDibujo;
         var contexto;
         var imagen;
         var estrellas;
         var xCampo;
         var yCampo;
         var gMasa;
         var G = 1;
  
         function WindowLoad() {
             areaDeDibujo = document.getElementById("AreaDeDibujo");
             contexto = areaDeDibujo.getContext("2d");
             imagen = new Image();
             imagen.src = "PuntoAmarillo.png";
             estrellas = [new Estrella(1, 0, 100, -1, 0),
                  new Estrella(1, 0, -100, 1, 0)]
             EmpezarAnimacion();
         }
  
         function EmpezarAnimacion() {
             if (!Modernizr.canvas) {
                 return;
             }
             window.setInterval(DibujarEscena, 20);
         }
  
         function DibujarEscena() {
  
             // Borramos la escena
             contexto.fillStyle = '#000000';
             contexto.fillRect(0, 0, areaDeDibujo.width, areaDeDibujo.height);
  
             // Calculamos el campo
             gMasa = 0;
             xCampo = 0;
             yCampo = 0;
             for (i = 0; i < estrellas.length; i++) {
                 gMasa += estrellas[i].masa;
                 xCampo += estrellas[i].xPos * estrellas[i].masa;
                 yCampo += estrellas[i].yPos * estrellas[i].masa;
             }
             // actualizamos la posición de los objetos
             for (i = 0; i < estrellas.length; i++) {
                 estrellas[i].Actualizar(xCampo, yCampo, gMasa);
             }
  
             // Dibujamos los objetos
             for (i = 0; i < estrellas.length; i++) {
                 estrellas[i].Dibujar(contexto, imagen);
             }
         }
     </script>
 </head>
 <body>
     <canvas id="AreaDeDibujo" height="640px" width="800px">
         Tu navegador no soporta el elemento canvas.
     </canvas>
    
 </body>
 </html>

Tu turno… ¡el Reto! – Extiende el uso de primitivas

En este caso te proponemos 3 retos:

1. Utiliza una primitiva para dibujar los objetos. En lugar de cargar una imagen, prueba diferentes primitivas para dibujar los objetos. Es más, parametriza el objeto para que permita que le pases una función que tome las coordenadas y dibuje lo que quiera.

2. Utiliza una secuencia de imágenes para animar los objetos que se pintan.

3. Haz que los objetos dejen un rastro para mostrar la ruta que han seguido.

Recuerda las descargas:

· WebMatrix

· Internet Explorer 9

· Visual Studio Express

· Web Standards Update for Microsoft Visual Studio 2010 SP1

Si tenéis cualquier comentario que hacer en este post o en twitter ( @esmsdn y el hashtag #retosmsdn) estaremos encantados de con vuestro feedback.

Boris Armenta -@borisarm- Developer Evangelist