Empezar con canvas de HTML5
Vamos a ver aquí algunos ejemplos de cómo hacer gráficos en el navegador con javascript, usando el tag <canvas> de HTML5. Actualmente no todos los navegadores lo soportan (Internet explorer 8 y anteriores), así que hay que seguir los ejemplos con un navegador adecuado. Google Chrome viene con una consola integrada, Firefox también tiene consola si le instalamos el plugin Firebug y es interesante instalar también el Firebug autocompleter, para que según escribimos las funciones, nos muestre las existentes.
Un HTML[editar]
Lo primero, un pequeño HTML con el <canvas>
<html> <head> </head> <body> <canvas id="canvas" width="500" height="500" style="border:1px solid black;">Tu navegador no soporta canvas</canvas> </body> </html>
Le hemos puesto un id para poder acceder fácilmente a él desde javascript y le hemos puesto un tamaño y un borde, para poder ver fácilmente en el navegador cual es nuestra área de dibujo.
Es importante saber que en el tag canvas los atributos height y width son distintos que si los metemos en style height y width. Los atributos nos van a determinar las coordendas de dibujo, además del tamaño. Es decir, en nuestro ejemplo, podremos dibujar con x entre 0 y 500, con y entre 0 y 500. El estilo CSS height y width nos da el tamaño del canvas, pero no nos cambia las coordenadas. Si ponemos esto
<canvas id="canvas" width="500" height="500" style="width:1000px;height=1000px;">
el canvas ocuparé en pantalla 1000x1000 pixels tal cual indica el estilo CSS, pero en nuestro código para dibujar la coordenada 500,500 corresponderá al pixel 1000,1000, es decir, se estira/escala todo al doble.
Si no ponemos atributos width y height, el navegador dará unos valores por defecto, 150 de ancho y 75 de alto en el caso de Firefox.
Obtener el contexto[editar]
Para dibujar en el canvas no se usan funciones javascript ni métodos del propio canvas. Es necesario pedirle al Canvas un "contexto" que es el que realmente tiene los métodos de dibujo. Para ello, el código javascript es
var canvas=document.getElementById("canvas"); var ctx=c.getContext("2d");
Este contexto ctx es el que tiene todos los métodos necesarios para dibujar.
Dibujar figuras[editar]
Nuestro dibujo está formado por "paths" y cada "path" es algo que dibujamos. Un path puede ser una línea, un polígono abierto o cerrado, varias líneas no consecutivas, varios polígonos, en fin, lo que queramos y para tratarlo todo como un conjunto.
Dibujar líneas rectas[editar]
Para dibujar un triángulo, hacemos la siguiente secuencia
ctx.beginPath(); ctx.strokeStyle="red"; ctx.moveTo(100,100); ctx.lineTo(150,150); ctx.lineTo(100,150); ctx.closePath(); ctx.stroke();
- beginPath() es para indicar que vamos a comenzar una figura nueva.
- strokeStyle indica el color que queremos, por defecto es negro. Podemos poner un color al estilo CSS (un nombre reconocido como en este caso "red", un rgb en hexadecimal con la # delante, estilo "#FF0000", ...)
- moveTo() nos lleva al punto inicial de la figura.
- lineTo() dibuja una línea recta desde el punto anterior hasta el punto indicado
- closePath() cierra la figura, uniendo el último punto con el primero.
- stroke() hace que la figura se dibuje, con el color de strokeStyle y grosor 1 por defecto.
Pulsando el botón, puedes ver el resultado
Podemos hacer un úncio "path" compuesto por dos figuras separadas, basta usar el moveTo() entre medias.
ctx.beginPath(); ctx.strokeStyle="red"; ctx.moveTo(100,100); ctx.lineTo(150,150); ctx.lineTo(100,150); ctx.closePath(); ctx.moveTo(200,100); ctx.lineTo(200,150); ctx.lineTo(250,100); ctx.stroke();
esta vez no hemos cerrado el path, puedes verlo pulsando el botón
Dibujar arcos[editar]
Para el dibujo de arcos tenemos el método arc(). El arco es un trozo de círculo. Este método admite los siguientes parámetros en el siguiente orden
- x del centro del círculo
- y del centro del círculo
- radio del círculo
- Angulo inicial en radianes
- Angulo final en radianes
- Sentido del arco. Indica si se debe ir del ángulo inicial al final en sentido horario o en sentido antihorario. Este parámetro es un boolean, por defecto false para sentido horario y debemos poner true para sentido antihorario.
El ángulo de 0 radianes corresponde a un radio horizontal del círculo y hacia la derecha (eje x positivo). Los ángulos crecen según este radio gira en sentido horario, como se indica en la imagen
Por ejemplo, el siguiente código dibujar un arco que va desde 0 radianes a 0.5*PI radianes, correspondiente a un radio vertical hacia abajo
ctx.beginPath(); ctx.arc(150,100,50,0,0.5*Math.PI); ctx.stroke();
puedes ver el resultado pulsando el botón
Los arcos que dibujemos se integran con el path que estamos dibujando, por lo que podemos hacer mezcla de líneas rectas y arcos para obtener cosas como la siguiente
ctx.beginPath(); ctx.moveTo(100,50); // ctx.lineTo(200,50); No es necesario, el arco empieza en 200,50 y como se // integra con el path, se dibujará la línea desde 100,50 a 200,50. ctx.arc(200,75,25,1.5*Math.PI,2.0*Math.PI); // Arco que empieza en 200,50 y acaba en 225, 75 ctx.lineTo(225, 150); ctx.stroke();
Curva cuadrática[editar]
Si queremos los extremos de dos segmentos con una línea curva, podemos usar la función quadraticCurveTo(). Fíjate en la siguiente imagen
Imagina que queremos unir en el lado izquierdo de la figura el "último punto path" con el "punto final", ambos en los extremos de los segmentos negros gruesos. La curva cuadrática se obtiene prolongando dichos segmentos (línea fina) hasta que se cortan en el llamado "punto de control". La curva será tangente a ambos segmentos, yendo desde el "último punto path" en dirección al "punto de control", para ir girando y llegar también de forma tangente al "punto final", obteniendo el resultado del lado derecho de la figura.
A la función guadraticCurveTo() debemos pasarle la x,y del punto de control y la x,y del punto final. El "último punto path" es el último punto del path que estemos dibujando con los métodos que hemos ido viendo hasta ahora.
El siguiente código nos muestra algo parecido
ctx.beginPath(); ctx.moveTo(100,150); ctx.lineTo(200,50); ctx.quadraticCurveTo(225,25,250,50); ctx.lineTo(350,150); ctx.stroke();
y aquí puedes verlo funcionando
Curva Bezier[editar]
Esta es una curva similar a la cuadrática, pero algo más compleja. Tiene dos puntos de control. Une el último punto del path en curso con un punto final que indiquemos, teniendo en cuenta dos puntos de control, como se indica en la siguiente figura
La curva bezier comienza siendo tangente a la línea imaginaria formada por el último punto del path y el punto de control 1. Y termina siendo tangente a la línea imaginaria formada por el segundo punto de control y el punto final. Entre medias, es tangente al segmento que se obtiene a base de hacer varios puntos medios de segmentos, son los puntos naranjas que se ven en la figura.
El siguiente código crea una curva Bezier
ctx.beginPath(); ctx.moveTo(100,250); ctx.bezierCurveTo(120,0,300,200,400,250); ctx.stroke();
que puedes ver pulsando en el siguiente botón
Dibujar rectángulos[editar]
Para dibujar rectángulos tenemos varios métodos.
- fillRect(x,y,ancho,alto) dibuja un rectángulo relleno. Es totalmente independiente del path que estemos dibujando, por lo que no afacta a la posición de dibujo actual.
- strokeRect(x,y,ancho,alto) dibuja el borde de un rectángulo. También es independiente del path que estemos dibujando.
- clearRect(x,y,ancho,alto) borra un área rectángular, dejando el color de fondo que se tenga, habitualmente transparente.
- rect(x,y,ancho,alg) dibuja el borde de un rectángulo, pero este si se integra de una forma extraña con el path que estemos dibujando. No dibuja una línea desde el punto actual hasta el inicio del rectángulo, pero sí deja el punto actual al final del rectángulo para la siguiente línea que dibujemos.
Un ejemplo de los tres primeros, tres rectángulos concéntricos, el exterior sólo la línea, el del medio relleno, pero con el tercero le borramos un trozo en el centro
ctx.beginPath(); ctx.strokeRect(100,50,100,100); ctx.fillRect(120,70,60,60); ctx.clearRect(130,80,40,40); ctx.stroke();
Grosor de las líneas y estilo de esquinas[editar]
Podemos dar grosor a las líneas del borde, llamando a ctx.lineWidth, dando el grosor en pixels
ctx.lineWidth=10; ctx.beginPath(); ctx.moveTo(100,100); ctx.lineTo(300,100); ctx.stroke();
puedes verlo en el botón
Como ves la línea termina abruptamente, se pueden hacer redondeos con ctx.lineCap, que admite los valores "butt", que es el de defecto que acabamos de ver, "round" que prolonga la terminación de la línea con un semicírculo y "square", que prolonga la línea con un semicuadrado.
// Linea "butt" ctx.lineWidth=30; ctx.beginPath(); ctx.lineCap="butt"; // Valor defecto ctx.moveTo(100,50); ctx.lineTo(250,50); ctx.stroke(); ctx.beginPath(); ctx.lineCap="round"; ctx.moveTo(100,100); ctx.lineTo(250,100); ctx.stroke(); ctx.beginPath(); ctx.lineCap="square"; ctx.moveTo(100,150); ctx.lineTo(250,150); ctx.stroke();
Pudes ver aquí el resultado pulsando el botón
Para este ejemplo hemos tenido que empezar un path nuevo para cada línea. El motivo es que stroke() dibuja todas las líneas que llevemos creadas desde beginPath() y aplica el último lineCap (o lineWidth o strokeStyle) que hayamos puesto a todas ellas. Así que después de hacer una línea, llamamos a stroke() y comenzamos un nuevo path con los nuevos atributos que queramos.
Si queremos que los vértices de nuestras figuras sean redondeados, la propiedad es lineJoin. que admite "miter" que es la de defecto, "bevel" y "round"
// Linea "butt" ctx.lineWidth=30; ctx.beginPath(); ctx.lineJoin="miter"; // Valor defecto ctx.moveTo(100,50); ctx.lineTo(150,50); ctx.lineTo(150,150); ctx.stroke(); ctx.beginPath(); ctx.lineCap="bevel"; ctx.moveTo(200,50); ctx.lineTo(250,50); ctx.lineTo(250,150); ctx.stroke(); ctx.beginPath(); ctx.lineCap="round"; ctx.moveTo(300,50); ctx.lineTo(350,50); ctx.lineTo(350,150); ctx.stroke();
Gradientes e imagenes[editar]
Hasta ahora hemos puesto en strokeStyle un color, pero también admite un gradiente o una imagen, de forma que la línea ira rellena con ese gradiente o imagen de fondo. El gradiente debemos crearlo usando el método createLinearGradient() o createRadialGradient().
También podemos rellenar una figura cerrada con gradientes e imagenes, lo veremos un poco más adelante, pero lo que es la creación del gradiente o la imagen se hace igual que aquí.
createLinearGradiend()[editar]
El primer método define una línea imaginaria y hace que el color cambie desde el origen al final, indicando nosotros ambos colores. Las líneas que dibujemos con este gradiente, iran cambiando su color de acuerdo a él.
var gradiente = ctx.createLinearGradient(0,0,500,200); gradiente.addColorStop(0,'black'); gradiente.addColorStop(1,'white'); ctx.beginPath(); ctx.strokeStyle=gradiente; ctx.lineWidth=20; ctx.moveTo(100,50); ctx.lineTo(400,50); ctx.lineTo(400,150); ctx.lineTo(100,150); ctx.closePath(); ctx.stroke();
Fíjate que al haber hecho la línea del gradiente inclinada, desde la parte superior izquierda del área de dibujo a la inferior derecha, el rectángulo es más claro cuanto más abajo y la derecha esté. Y si las coordenadas de nuestro dibujo se pasan totalmente de la línea del gradiente, todo lo que esté fuera será del color de inicio o del final. Por ejemplo
var gradiente = ctx.createLinearGradient(200,75,300,125); gradiente.addColorStop(0,'black'); gradiente.addColorStop(1,'white'); ctx.beginPath(); ctx.strokeStyle=gradiente; ctx.lineWidth=20; ctx.moveTo(100,50); ctx.lineTo(400,50); ctx.lineTo(400,150); ctx.lineTo(100,150); ctx.closePath(); ctx.stroke();
También es importante saber que el gradiente solo admite llamar al método addColorStop con los índices 0 o 1, según sea el inicio o el final.
createRadialGradient()[editar]
Con este método, el gradiente se contruye de forma radial, empieza en un círculo con un color y va cambiando hacia otro círculo con otro color. Veamos un par de ejemplo, el primero con dos círculos concéntricos, el primero con radio cero. El segundo ejemplo son dos círculos de distinto centro, pero nuevamente con el primero de rado cero.
var gradiente1 = ctx.createRadialGradient(100,100,0,100,100,150); var gradiente2 = ctx.createRadialGradient(350,50,0,400,100,150);
Y esto es lo que pasa si dibujamos usando estos gradientes
var gradiente = ctx.createRadialGradient(100,100,0,100,100,150); gradiente.addColorStop(0,'white'); gradiente.addColorStop(1,'green'); ctx.beginPath(); ctx.strokeStyle=gradiente; ctx.lineWidth=50; ctx.moveTo(0,0); ctx.lineTo(200,200); ctx.stroke(); gradiente = ctx.createRadialGradient(350,50,0,400,100,150); gradiente.addColorStop(0,'white'); gradiente.addColorStop(1,'green'); ctx.beginPath(); ctx.strokeStyle=gradiente; ctx.moveTo(300,0); ctx.lineTo(500,200); ctx.stroke();
createPattern()[editar]
En vez de un gradiente como en los dos métodos anteriores, podemos usar una imagen cualquiera. El método es createPattern(), que admite como primer parámetro un objeto Image y como segundo parámetro un string cuyos valores pueden ser "repeat", "repeat-x", "repeat-y", y "no-repeat", siendo "repeat" el valor por defecto si no especificamos nada.
El objeto imagen se crea de esta forma
var imagen = new Image(); imagen.src="url de la imagen"; ctx.strokeStyle = ctx.createPattern(imagen, "repeat");
el problema con este código es que debemos esperar la carga de la imagen, por ello es mejor poner la creación del pattern en un evento onload que saltará cuando la imagen esté totalmente cargada
var imagen = new Image(); imagen.onload = function() { ctx.strokeStyle = ctx.createPattern(this, "repeat"); } imagen.src="url de la imagen"
El siguiente código dibuja una línea "gorda" con una imagen de fondo
var imagen = new Image(); imagen.onload = function() { ctx.strokeStyle = ctx.createPattern(this, "repeat"); ctx.lineWidth=100; ctx.beginPath(); ctx.moveTo(0,0); ctx.lineTo(200,200); ctx.lineto(400,0); ctx.stroke(); } imagen.src="http://www.chuidiang.com/iconos/serpi.gif"
y esto sería lo que se ve
Rellenar el polígono[editar]
Veamos ahora como aplicar todo lo visto de color y relleno de líneas para rellenos de polígonos.
Rellenar con color =[editar]
Podemos rellenar el path eligiendo un color con ctx.fillStyle y llamando al método fill()
ctx.beginPath(); ctx.strokeStyle="red"; ctx.fillStyle="green"; ctx.moveTo(100,100); ctx.lineTo(150,150); ctx.lineTo(100,150); ctx.stroke(); ctx.fill();
Aunque no cerremos el path, se rellena el polígono, como puedes ver
Hay una curiosidad que es importante saber si hacemos polígonos incluidos dentro de otros polígonos, por ejemplo, mira estos dos ejemplos con un cuadrado dentro de otro y llamando al método fill()
// cuadro 1 con cuadro dentro ctx.beginPath(); ctx.strokeStyle="red"; ctx.fillStyle="lightgreen"; ctx.moveTo(50,50); ctx.lineTo(50,150); ctx.lineTo(150,150); ctx.lineTo(150,50); ctx.closePath(); ctx.moveTo(75,75); ctx.lineTo(75,125); ctx.lineTo(125,125); ctx.lineTo(125,75); ctx.closePath(); ctx.stroke(); ctx.fill(); // cuadro 2 con cuadro dentro ctx.beginPath(); ctx.strokeStyle="red"; ctx.fillStyle="lightgreen"; ctx.moveTo(250,50); ctx.lineTo(250,150); ctx.lineTo(350,150); ctx.lineTo(350,50); ctx.closePath(); ctx.moveTo(275,75); ctx.lineTo(325,75); ctx.lineTo(325,125); ctx.lineTo(275,125); ctx.closePath(); ctx.stroke(); ctx.fill();
Ambos cuadros son iguales salvo por un pequeño detalle. En el primer caso, hemos pintado el cuadro interior en el mismo sentido horario que el exterior, mientras que en el segundo caso lo hemos hecho en sentido contrario. El algoritmo de relleno se conoce como Non Zero Rule. La idea de este algoritmo de relleno es la siguiente
- Cogemos un punto que queremos saber si está o no dentro del polígono. Imagina que cogemos un punto en el centro de cada figura.
- Trazamos desde ese punto una línea recta en cualquier dirección, hasta el infinito. Para simplificar, imagina que la línea es vertical hacia arriba.
- Contamos cada segmento de la figura que cruzamos. Si el segmento va de izquierda a derecha, sumamos uno. Si va en sentido contrario restamos uno.
- Si el resultado es 0, es que el punto está fuera. Si el resultado es distinto de cero, entonces está dentro.
En nuestra primera figura, con dos cuadrados uno dentro de otro en el mismo sentido, desde el punto interior hacia arriba cortaremos dos segmentos, las dos partes superiores de cada cuadrado. Como ambas van el mismo sentido, la cuenta nos sale 2, así que el punto está dentro del polígono y se rellena de verde.
En la segunda figura, también cortamos las dos partes superiores de cada cuadrado, pero una va hacia la derecha y la otra hacia la izquierda, así que la cuenta nos sale 0. El punto está fuera del polígono y no se rellena. Por eso nos sale el cuadrado blanco (sin rellenar) dentro.
Rellenar con gradientes e imagenes[editar]
De la misma forma que hicimos con las líneas, podemos rellenar un polígono con gradientes y con imágenes. Se hace igual, pero usando ctx.fillStyle y ctx.fill() en vez de de ctx.strokeStyle y ctx.stroke(). No vamos a hacer todos los ejemplos, sólo uno con la imagen
imagen = new Image(); imagen.onload = function() { ctx.fillStyle = ctx.createPattern(this, "repeat"); ctx.beginPath(); ctx.moveTo(0,0); ctx.lineTo(200,200); ctx.lineTo(400,0); ctx.closePath(); ctx.stroke(); // Pintamos también el borde, no es necesario. ctx.fill(); }; imagen.src="http://www.chuidiang.com/iconos/serpi.gif";
que puedes ver aquí funcionando.
Rellenar con otro canvas[editar]
A la hora de rellenar o dibujar líneas con imagenes (con pattern), podemos usar en vez de imagenes de una URL imagenes de otro canvas, bien que ya esté en pantalla, bien creándolo al vuelo. En el siguiente ejemplo creamos un canvas al vuelo, no visible en la página y lo usamos para rellenar un rectángulo en un segundo canvas que sí está visible.
Primero creamos el canvas al vuelo y dibujamos algo en él, dándole previamente un tamaño de 20x20.
canvasAlVuelo=document.createElement("canvas"); canvasAlVuelo.width=20; canvasAlVuelo.height=20; ctx=canvasAlVuelo.getContext('2d'); ctx.beginPath(); ctx.moveTo(0,0); ctx.lineTo(20,20); ctx.moveTo(20,0); ctx.lineTo(0,20); ctx.strokeStyle="green"; ctx.stroke();
y ahora, simplemente usamos el canvas c en el createPattern de nuestro canvas visible
var canvas = document.getElementById("idCanvasVisible"); var ctx = canvas.getContext('2d'); var pat = ctx.createPattern(canvasAlVuelo, 'repeat'); ctx.rect(0,0,100,100); ctx.fillStyle=pat; ctx.fill();
Puedes ver el resultado dándole al botón