El elemento canvas
proporciona un API para dibujar líneas, formas, imágenes, texto, etc en 2D, sobre el lienzo que del elemento. Este API ya está siento utilizado de manera exhaustiva, en la creación de fondos interactivos, elementos de navegación, herramientas de dibujado, juegos o emuladores. Éste elemento canvas
es uno de los elementos que cuenta con una de las mayores especificaciones dentro de HTML5. De hecho, el API de dibujado en 2D se ha separado en un documento a parte.
Un ejemplo de lo que se puede llegar a crear, es la recreación del programa MS Paint incluido en Windows 95.
Para comenzar a utilizar el elemento canvas
, debemos colocarlo en el documento. El marcado es extremadamente sencillo:
<canvas id="tutorial" width="150" height="150"></canvas>
Éste nos recuerda mucho al elemento img
, pero sin los atributos src
y alt
. En realidad, el elemento canvas
solamente tiene los dos atributos mostrados en el ejemplo anterior: width
y height
, ambos opcionales y que pueden establecerse mediante las propiedades DOM. Cuando estos dos atributos no se especifican, el lienzo (canvas) inicial será de 300px
de ancho por 150px
de alto. Este elemento, además y como muchos otros, pueden modificarse utilizando CSS. Podemos aplicar cualquier estilo, pero las reglas afectarán al elemento, no a lo dibujado en el lienzo.
En la actualidad, todos los navegadores son compatibles con el elemento canvas
, la diferencia entre ellos radica en qué funcionalidades del API han implementado.
Ahora que el elemento canvas
está definido en el documento, la manera de dibujar en él es a través de JavaScript. El primer paso es obtener el contexto de dibujado. <canvas>
crea una superficie de dibujo de tamaño fijo que expone uno o más contextos de representación, que se utilizan para crear y manipular el contenido mostrado. En el contexto de representación 2D, (existe otro contexto de representación en 3D, llamado WebGL), el canvas
está inicialmente en blanco y, para mostrar algo, es necesario el acceso de un script
al contexto de representación para que pueda dibujar en él. El método DOM getContext
sirve para obtener el contexto de representación y sus funciones de dibujo.
var canvas = document.getElementById('tutorial'); var ctx = canvas.getContext('2d');
El siguiente ejemplo dibujaría dos rectángulos que se cruzan, uno de los cuales tiene transparencia alfa.
<html> <head> <script type="application/javascript"> window.onload = function() { var canvas = document.getElementById("canvas"); if (canvas.getContext) { var ctx = canvas.getContext("2d"); ctx.fillStyle = "rgb(200,0,0)"; ctx.fillRect (10, 10, 55, 50); ctx.fillStyle = "rgba(0, 0, 200, 0.5)"; ctx.fillRect (30, 30, 55, 50); } }; </script> </head> <body> <canvas id="canvas" width="150" height="150"></canvas> </body> </html>
Para empezar a dibujar formas, es necesario hablar primero de la cuadrícula del canvas
o espacio de coordenadas. Normalmente, una unidad en la cuadrícula corresponde a un px en el lienzo. El punto de origen de esta cuadrícula se coloca en la esquina superior izquierda (coordenadas(0,0)). Todos los elementos se colocan con relación a este origen.
Desgraciadamente, canvas
solamente admite una forma primitiva: los rectángulos, por lo que el resto de las formas deberán crearse mediante la combinación de una o más funciones.
Existen tres funciones que dibujan un rectángulo en el lienzo:
fillRect(x,y,width,height)
: dibuja un rectángulo relleno
.strokeRect(x,y,width,height)
: dibuja un contorno rectangular
.clearRect(x,y,width,height)
: borra el área especificada y hace que sea totalmente transparente.Cada una de estas funciones tiene los mismos parámetros. x
e y
especifican la posición en el lienzo. width
es la anchura y height
la altura.
var canvas = document.getElementById('tutorial'); var ctx = canvas.getContext('2d'); ctx.fillRect(25,25,100,100); ctx.clearRect(45,45,60,60); ctx.strokeRect(50,50,50,50);
La función fillRect
dibuja un gran cuadrado negro de 100x100 px. La función clearRect
elimina un cuadrado de 60x60 px del centro y finalmente el strokeRect
dibuja un contorno rectangular de 50x50 px en el interior del cuadrado despejado.
A diferencia de las funciones de rutas que veremos en la siguiente sección, las tres funciones de rectángulo se dibujan inmediatamente en el lienzo.
Gracias al API 2D, es posible movernos a través del canvas y dibujar líneas y formas. Las rutas son utilizadas para dibujar formas (líneas, curvas, polígonos, etc) que de otra forma no podríamos conseguir.
El primer paso para crear una ruta es llamar al método beginPath
. Internamente, las rutas se almacenan como una lista de subrutas (líneas, arcos, etc.) que, en conjunto, forman una figura. Cada vez que se llama a este método, la lista se pone a cero y podemos empezar a dibujar nuevas formas. El paso final sería llamar al método closePath
: este método intenta cerrar la forma trazando una línea recta desde el punto actual hasta el inicial. Si la forma ya se ha cerrado o hay solo un punto en la lista, esta función no hace nada.
var canvas = document.getElementById('tutorial'); var context = canvas.getContext('2d');
context.beginPath(); //... path drawing operations context.closePath();
El siguiente paso es dibujar la forma como tal. Para ello, disponemos de algunas funciones de dibujado de líneas y arcos, que especifican las rutas a dibujar.
lineTo
Para dibujar líneas rectas utilizamos el método lineTo
. Este método toma dos argumentos x
e y
, que son las coordenadas del punto final de la línea. El punto de partida depende de las rutas anteriores.
En el siguiente ejemplo se dibujan dos triángulos, uno relleno y el otro únicamente trazado. En primer lugar se llama al método beginPath
para iniciar una nueva ruta. A continuación, utilizamos el método moveTo
para mover el punto de partida hasta la posición deseada. Finalmente se dibujan dos líneas que forman dos lados del triángulo. Al llamar al método closePath
, éste traza una línea al origen, completando el triángulo.
// Triángulo relleno ctx.beginPath(); ctx.moveTo(25,25); ctx.lineTo(105,25); ctx.lineTo(25,105); ctx.closePath(); ctx.fill();
// Triángulo trazado ctx.beginPath(); ctx.moveTo(125,125); ctx.lineTo(125,45); ctx.lineTo(45,125); ctx.closePath(); ctx.stroke();
En ambos casos utilizamos dos funciones de pintado diferentes: stroke
y fill
. Stroke
se utiliza para dibujar una forma con contorno, mientras que fill
se utiliza para pintar una forma sólida.
Para dibujar arcos o círculos se utiliza el método arc
(la especificación también describe el método arcTo
). Este método toma cinco parámetros: x
e y
, el radio, startAngle
y endAngle
(que definen los puntos de inicio y final del arco en radianes) y anticlockwise
(un valor booleano que, cuando tiene valor true
dibuja el arco de modo levógiro y viceversa cuando es false
).
Un ejemplo algo más complejo que los anteriores utilizando el método arc
sería:
for(var i=0;i<4;i++){ for(var j=0;j<3;j++){ ctx.beginPath(); var x = 25+j*50; // coordenada x var y = 25+i*50; // coordenada y var radius = 20; // radio del arco var startAngle = 0; // punto inicial del círculo var endAngle = Math.PI+(Math.PI*j)/2; // punto final var anticlockwise = i%2==0 ? false : true; ctx.arc(x,y,radius,startAngle,endAngle, anticlockwise); if (i>1){ ctx.fill(); } else { ctx.stroke(); } } }
Nota
Los métodos
arc
,bezier
yquadratic
utilizan radianes, pero si preferimos trabajar en grados, es necesario convertirlo a radianes. Esta el la operación que tenemos que llevar a cabo:
var radians = degrees * Math.PI / 180;
moveTo
Disponemos de la función moveTo
, que en realidad, aunque no dibuja nada, podemos imaginárnosla como 'si levantas un lápiz desde un punto a otro en un papel y lo colocas en el siguiente'. Esta función es utilizada para colocar el punto de partida en otro lugar o para dibujar rutas inconexas.
ctx.beginPath(); ctx.arc(75,75,50,0,Math.PI*2,true); // círculo exterior ctx.closePath(); ctx.fill();
ctx.fillStyle = '#FFF'; ctx.beginPath(); ctx.arc(75,75,35,0,Math.PI,false); // boca (dextrógiro) ctx.closePath(); ctx.fill();
ctx.moveTo(65,65); ctx.beginPath(); ctx.arc(60,65,5,0,Math.PI*2,true); // ojo izquierdo ctx.closePath(); ctx.fill();
ctx.moveTo(95,65); ctx.beginPath(); ctx.arc(90,65,5,0,Math.PI*2,true); // ojo derecho ctx.closePath(); ctx.fill();
Si queremos aplicar colores a una forma, hay dos características importantes que podemos utilizar: fillStyle
y strokeStyle
.
fillStyle = color strokeStyle = color
strokeStyle
se utiliza para configurar el color del contorno de la forma y fillStyle
es para el color de relleno. Color puede ser una cadena que representa un valor de color CSS, un objeto degradado o un objeto modelo.
// todos ellos configuran fillStyle a 'naranja' (orange) ctx.fillStyle = "orange"; ctx.fillStyle = "#FFA500"; ctx.fillStyle = "rgb(255,165,0)"; ctx.fillStyle = "rgba(255,165,0,1)";
Ejemplo de fillStyle
:
function draw() { for (var i=0;i<6;i++){ for (var j=0;j<6;j++){ ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' + Math.floor(255-42.5*j) + ',0)'; ctx.fillRect(j*25,i*25,25,25); } } }
Ejemplo de strokeStyle
:
function draw() { for (var i=0;i<6;i++){ for (var j=0;j<6;j++){ ctx.strokeStyle = 'rgb(0,' + Math.floor(255-42.5*i) + ',' + ath.floor(255-42.5*j) + ')'; ctx.beginPath(); ctx.arc(12.5+j*25,12.5+i*25,10,0,Math.PI*2,true); ctx.stroke(); } } }
A través del contexto, es posible generar degradados lineales, radiales o rellenos a través de patrones, que pueden ser utilizados en el método fillStyle
del canvas. Los degradados funcionan de una manera similar a los definidos en CSS3, donde se especifican el inicio y los pasos de color para el degradado.
Los patrones, por otra parte, permiten definir una imagen como origen y especificar el patrón de repetido, de nuevo de manera similar a como se realizaba con la propiedad background-image
de CSS. Lo que hace interesante al método createPattern
es que como origen podemos utilizar una imagen, un canvas o un elemento de vídeo.
Un simple gradiente se crea de la siguiente manera:
var canvas = document.getElementById('tutorial'); var ctx = canvas.getContext('2d'); var gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
gradient.addColorStop(0, '#fff'); gradient.addColorStop(1, '#000'); ctx.fillStyle = gradient; ctx.fillRect(0, 0, canvas.width, canvas.height);
El código anterior creamos un degradado lineal al que aplicamos dos pasos de color. Los argumentos de createLinearGradient
son el punto de inicio del degradado (x1
e y1
) y el punto final del degradado (x2
e y2
). En este caso el degradado comienza en la esquina superior izquierda, y termina en la esquina inferior izquierda. Utilizamos este degradado para pintar el fondo de un rectángulo.
Los degradados radiales son muy similares, con la excepción que definimos el radio después de cada coordenada:
var canvas = document.getElementById('tutorial'); var ctx = canvas.getContext('2d'); gradient = ctx.createRadialGradient(canvas.width/2, canvas.height/2, 0, canvas.width/2, canvas.height/2, 150); gradient.addColorStop(0, '#fff'); gradient.addColorStop(1, '#000'); ctx.fillStyle = gradient; ctx.fillRect(0, 0, canvas.width, canvas.width);
La única diferencia es la manera en la que se ha creado el degradado. En este ejemplo, el primer punto del degradado se define en el centro del canvas, con radio cero. El siguiente punto se define con un radio de 150px
pero su origen es el mismo, lo que produce un degradado circular.
Los patrones son incluso más sencillos de utilizar. Es necesaria una fuente (como una imagen, un canvas o un elemento de vídeo) y posteriormente utilizamos esta fuente en el método createPattern
y el resultado de éste en el método fillStyle
. La única consideración a tener en cuenta es que, al utilizar elementos de imagen o vídeo, éstos tienen que haber terminado de cargarse para poder utilizarlos. En el siguiente ejemplo, expandimos el canvas para que ocupe toda la ventana, y cuando se carga la imagen, la utilizamos como patrón de repetición.
var canvas = document.getElementById('tutorial'); var img = document.createElement('img'); var ctx = canvas.getContext('2d'); canvas.width = window.innerWidth; canvas.height = window.innerHeight;
img.onload = function () { ctx.fillStyle = ctx.createPattern(this, 'repeat'); ctx.fillRect(0, 0, canvas.width, canvas.height); }; img.src = 'avatar.jpg';
Además de dibujar formas opacas en el lienzo, también podemos dibujar formas semitransparentes. Esto se hace mediante el establecimiento de la propiedad globalAlpha
o podríamos asignar un color semitransparente al trazo y/o al estilo de relleno.
globalAlpha = transparency value
Esta propiedad aplica un valor de transparencia a todas las formas dibujadas en el lienzo y puede ser útil si deseas dibujar un montón de formas en el lienzo con una transparencia similar; ya que debido a que las propiedades strokeStyle
y fillStyle
aceptan valores de color CSS3, podemos utilizar la siguiente notación para asignarles un color transparente.
function draw() { // dibujar fondo ctx.fillStyle = '#FD0'; ctx.fillRect(0,0,75,75); ctx.fillStyle = '#6C0'; ctx.fillRect(75,0,75,75); ctx.fillStyle = '#09F'; ctx.fillRect(0,75,75,75); ctx.fillStyle = '#F30'; ctx.fillRect(75,75,150,150); ctx.fillStyle = '#FFF'; // establecer valor de transparencia ctx.globalAlpha = 0.2; // Dibujar círculos semitransparentes for (var i=0;i<7;i++){ ctx.beginPath(); ctx.arc(75,75,10+10*i,0,Math.PI*2,true); ctx.fill(); } }
Al igual que tenemos la posibilidad mover el lápiz por el canvas con el método moveTo
, podemos definir algunas transformaciones como rotación, escalado, transformación y traslación (similares a las conocidas de CSS3).
translate
Éste método traslada el centro de coordenadas desde su posición por defecto (0, 0) a la posición indicada.
ctx.translate(x, y);
rotate
Éste método inicia la rotación desde su posición por defecto (0,0). Si se rota el canvas desde esta posición, el contenido podría desaparecer por los límites del lienzo, por lo que es necesario definir un nuevo origen para la rotación, dependiendo del resultado deseado.
ctx.rotate(angle);
Éste método sólo precisa tomar un parámetro, que es el ángulo de rotación que se aplicará al marco. Este parámetro es una rotación dextrógira medida en radianes.
function draw() { ctx.translate(75,75); for (i=1;i<6;i++){ // Desplazarse or los anillos (desde dentro hacia fuera) ctx.save(); ctx.fillStyle = 'rgb('+(51*i)+','+(255-51*i)+',255)'; for (j=0;j<i*6;j++){ // dibujar puntos individuales ctx.rotate(Math.PI*2/(i*6)); ctx.beginPath(); ctx.arc(0,i*12.5,5,0,Math.PI*2,true); ctx.fill(); } ctx.restore(); } }
scale
El siguiente método de transformación es el escalado. Se utiliza para aumentar o disminuir las unidades del tamaño de nuestro marco. Este método puede usarse para dibujar formas ampliadas o reducidas.
ctx.scale(x, y);
x
e y
definen el factor de escala en la dirección horizontal y vertical respectivamente. Los valores menores que 1.0 reducen el tamaño de la unidad y los valores mayores que 1.0 aumentan el tamaño de la unidad. Por defecto, una unidad en el área de trabajo equivale exactamente a un píxel. Si aplicamos, por ejemplo, un factor de escalado de 0.5, la unidad resultante será 0.5 píxeles, de manera que las formas se dibujarán a mitad de su tamaño.
Como estamos utilizando scripts para controlar los elementos canvas
, resulta muy fácil hacer animaciones (interactivas). Sin embargo, el elemento canvas
no fue diseñado para ser utilizado de esta manera (a diferencia de flash) por lo que existen limitaciones.
Probablemente, la mayor limitación es que una vez que se dibuja una forma, se queda de esa manera. Si necesitamos moverla, tenemos que volver a dibujar dicha forma y todo lo que se dibujó anteriormente.
Los pasos básicos a seguir son los siguientes:
clearRect
.canvas
: si se va a modificar alguna configuración (estilos, transformaciones) que afectan al estado de canvas
.Las formas se dibujan en el lienzo mediante el uso de los métodos canvas
directamente o llamando a funciones personalizadas. Necesitamos una manera de ejecutar nuestras funciones de dibujo en un período de tiempo. Hay dos formas de controlar una animación como ésta. En primer lugar está las funciones setInterval
y setTimeout
, que se pueden utilizar para llamar a una función específica durante un período determinado de tiempo.
setInterval (animateShape, 500); setTimeout (animateShape, 500);
El segundo método que podemos utilizar para controlar una animación es el input del usuario. Si quisiéramos hacer un juego, podríamos utilizar los eventos de teclado o de ratón para controlar la animación. Al establecer EventListeners
, capturamos cualquier interacción del usuario y ejecutamos nuestras funciones de animación.
Ejercicio 7