1. Code
  2. JavaScript

Dibujando Con Two.js

Los gráficos avanzados son una gran parte de la web en estos días, pero hay un par de renderizadores diferentes en la mezcla. Podrías usar lienzo, por supuesto; Pero SVG y WebGL también son opciones. En este tutorial, revisaremos una biblioteca de dibujo relativamente nueva, two.js, que proporciona una API que hace lo mismo con los tres renderizadores. Si estás listo, ¡vamos a verlo!
Scroll to top

Spanish (Español) translation by Elías Nicolás (you can also view the original English article)

Los gráficos avanzados son una gran parte de la web en estos días, pero hay un par de renderizadores diferentes en la mezcla. Podrías usar lienzo, por supuesto; Pero SVG y WebGL también son opciones. En este tutorial, revisaremos una biblioteca de dibujo relativamente nueva, two.js, que proporciona una API que hace lo mismo con los tres renderizadores. Si estás listo, ¡vamos a verlo!


Paso 1 - Configuración

El primer paso es crear una instancia two y ponerla en la página. El constructor Two toma un objeto con una serie de parámetros:

1
2
    var two = new Two({
3
       fullscreen: true 
4
    });

En este caso, estamos usando la opción de fullscreen, que hace que el área de dibujo ocupe toda la ventana del navegador. Si quisiéramos que nuestra área de dibujo tuviera un tamaño específico, podríamos usar las propiedades width y height en su lugar; ambos toman un número para un valor de píxel. También está el parámetro autostart; si se establece en verdadero, todas las animaciones se ejecutarán de inmediato cuando se cargue la página.

También está el parámetro type: esto decide qué renderizador se usará. Puedes elegir entre canvas, SVG, y WebGl. Sin embargo, no solo escribe el nombre: usa una constante de biblioteca de clases: Two.Types.canvas, Two.Types.svg, o Two.Types.webgl. Para ser claros, two.js usará por defecto el uso de SVG; no hace ningún tipo de detección de características para ver qué es compatible con el navegador. Tendrá que hacerlo por su cuenta (y creo que es una buena idea: herramientas pequeñas, una cosa bien y todo eso).

Entonces, una vez que tenemos una instancia Two, ¿qué hacemos con ella? Primero, querrás adjuntarlo a la página. Tiene un método appendTo que toma un elemento HTML como parámetro, así que configuremos esto:

1
2
<div id="main"></div>
3
<script src="./two.min.js"></script>
4
<script src="./main.js"></script>

Luego, en main.js, comenzamos con esto:

1
2
var el = document.getElementById("main"),
3
    two = new Two({ 
4
        fullscreen: true
5
    });
6
7
two.appendTo(el);

Con todo esto configurado, estamos listos para dibujar algunas formas.


Paso 2 - Dibujar formas básicas

Comenzaremos con formas básicas; Si bien podemos crear nuestras propias formas complejas con new Two.Polygon, las formas más simples se pueden hacer con algunos métodos prácticos.

Vamos a empezar con los círculos. La función makeCircle toma tres parámetros:

1
2
var circle = two.makeCircle(110, 110, 100);
3
circle.fill = "#881111";
4
5
two.update();

Revisaremos de abajo hacia arriba: la llamada a two.update, son áreas de dibujo y en realidad representan el contenido. Retrocediendo al círculo, los dos primeros parámetros son las coordenadas x e y para el centro del círculo. Entonces, el tercer parámetro es el radio para el círculo. Todas las funciones two.make... devuelven un objeto Two.Polygon. A medida que avanzamos en este tutorial, verás varias propiedades y métodos que puedes usar en estas formas. Aquí está el primero: fill. Como puede imaginar, establece el color de relleno: cualquier CSS válido funcionará.

El resultado debería verse así:

Ahora, ¿qué pasa con los rectángulos? El método two.makeRectangle toma cuatro parámetros. Al igual que el círculo, los dos primeros parámetros marcan las coordenadas x e y para el centro del rectángulo. Luego, param tres es width y param cuatro es height del rectángulo.

1
2
var rect = two.makeRectangle(115, 90, 150, 100);
3
rect.fill = "orange";
4
rect.opacity = 0.25;
5
rect.noStroke();
6
7
two.update();

Una vez más, estamos usando la propiedad fill. También estamos usando la propiedad opacity, que acepta un valor decimal entre 0 y 1; Aquí tenemos un cuarto de opacidad. Finalmente, estamos usando el método noStroke, que elimina el trazo (borde) del rectángulo. Esto es lo que tenemos:

Los puntos suspensivos también son bastante simples: como puede imaginar, los dos primeros parámetros establecen el centro de la elipse. Entonces, tenemos ancho y alto:

1
2
var ellipse = two.makeEllipse(100, 40, 90, 30);
3
ellipse.stroke = "#112233";
4
ellipse.linewidth = 5;
5
ellipse.noFill();
6
7
two.update();

Para nuevas propiedades: tenemos stroke, que establece el color del borde; para establecer el ancho de ese borde, usamos la propiedad linewidth. Entonces, ¿recuerdas noStroke? El método noFill es el mismo, excepto que elimina el color de relleno de nuestra forma (sin eso, nuestras formas se predeterminan a un relleno blanco).

Por supuesto, las formas más simples son las líneas.

1
2
var line = two.makeLine(10, 10, 110, 210);
3
line.linewidth = 10;
4
line.stroke = "rgba(255, 0, 0, 0.5)";

Los primeros dos parámetros son la x y la y para un extremo de la línea; El segundo conjunto son para el otro extremo.

Probablemente la forma más incómoda de crear es la curva. El método two.makeCurve toma todos los conjuntos de parámetros x, y que desee, ya que cada par es un punto donde la línea se curva. Entonces, el último parámetro es booleano: hazlo true si la forma está abierta, lo que significa que los extremos no se conectan. Si desea que two.js dibuje una línea que conecta los dos extremos de las curvas, eso debería ser false.

1
2
var curve = two.makeCurve(110, 100, 120, 50, 140, 150, 160, 50, 180, 150, 190, 100, true);
3
curve.linewidth = 2;
4
curve.scale = 1.75;
5
curve.rotation = Math.PI / 2; // Quarter-turn

6
curve.noFill();

Usted sabe el linewidth, pero ¿qué pasa con scale? Podemos usar esto para reducir o expandir nuestra forma; Aquí, estamos expandiendo la forma en un 175%. Luego, podemos usar rotation para rotar nuestra forma por un número de radianes; Estamos haciendo 90 grados, que son radianes medio PI.

Finalmente, puedes pensar que ya que hemos abierto la forma, no obtendremos un relleno; pero eso no es cierto. Una curva no cerrada todavía tendrá un relleno, por lo que estamos usando noFill para eliminar el relleno y terminar solo con la curva.

El último tipo de forma es el catch-all: es el polígono general. En realidad, es casi como la curva, excepto que las líneas van directamente de un punto a otro.

1
2
var poly = two.makePolygon(110, 100, 120, 50, 140, 150, 160, 50, 180, 150, 190, 100);
3
poly.linewidth = 4;
4
poly.translation = new Two.Vector(60, 60);
5
poly.stroke = "#cccccc";
6
poly.fill = "#ececec";

Al igual que con la curva, tenemos tantos pares de coordenadas como nos gustaría, y luego el booleano abierto; Aquí lo configuramos como false, por lo que la forma se cerrará.

También estamos configurando translation aquí; Esto nos permite mover la forma hacia la izquierda o hacia la derecha y hacia arriba o hacia abajo. Estamos configurando la propiedad translation a una instancia de Two.Vector. El constructor Two.Vector toma dos parámetros: una x y una y. Estas terminan siendo las coordenadas del centro de la forma. No es necesario crear un nuevo vector para esto; simplemente puede asignar el directorio de valores x e y:

1
2
poly.translation.x = 60;
3
poly.translation.y = 60;

Esto es lo que obtenemos:


Paso 3 - Haciendo grupos

Hasta ahora, hemos estado trabajando con objetos de formas individuales; sin embargo, es posible agrupar formas e interactuar con ellas como una sola pieza.

Puedes hacer un grupo con el método two.makeGroup. Luego, podemos usar su método add para agregar una forma al grupo.

1
2
var group = two.makeGroup(),
3
    rect = two.makeRectangle(0, 0, 100, 100),
4
    circ = two.makeCircle(50, 50, 50);</p>

5
6
rect.fill = "red";
7
circ.fill = "blue";
8
9
group.add(rect);
10
group.add(circ);
11
12
two.update();

Si ejecuta esto, es bastante básico; Al igual que obtendría sin los bits de group.

Pero entonces, podemos trabajar con el grupo, usando cualquiera de las transformaciones que podemos hacer en una forma individual. Por ejemplo, ¿qué tal una traducción?

1
2
group.translation.x = 100;
3
group.translation.y = 100;
4
5
two.update();

Al igual que con las formas regulares, los grupos se ordenan desde el principio hasta el final, a medida que se crean. Sin embargo, si agrega una forma a un grupo y luego a otro grupo, se eliminará del primer grupo. Esto es genial si necesitas cambiar el orden de adelante hacia atrás de las formas cuando animas (lo cual haremos). Entonces, si empezamos con esto:

1
2
var topGroup = two.makeGroup(),
3
    bottomGroup = two.makeGroup(),
4
    rect = two.makeRectangle(100, 100, 100, 100),
5
    circ = two.makeCircle(150, 150, 50);
6
rect.fill = "red";
7
circ.fill = "blue";
8
9
topGroup.add(rect);
10
topGroup.add(circ);  
11
12
two.update();

Tenemos lo mismo que arriba:

Pero, si agregamos la rect al bottomGroup en su lugar...

1
2
bottomGroup.add(rect);

Ahora, nuestra plaza está en la parte superior.

Paso 4 - Animando Formas

Finalmente, hablemos de animación. Ya sabes que two.js representa las formas que has creado cuando llamas a two.update(). Si llamas two.play() en su lugar, es como llamar a update() repetidamente, usando Request Animation Frame. Cada vez que esto sucede, two.js dispara un evento de "actualización". Así es como podemos producir animación: escuche el evento de "actualización"; y cuando suceda, ejecute una función para configurar el siguiente fotograma.

Hasta ahora, nuestros ejemplos han sido bastante simples, así que avancemos un poco más: crearemos un planeta en órbita con su propia luna en órbita. Recuerda, comenzamos creando dos instancias:

1
2
var el = document.getElementById("main"),
3
    two = new Two({ 
4
        fullscreen: true
5
    }).appendTo(el);

A continuación, necesitamos configurar algunas variables.

1
2
var earthAngle = 0,
3
    moonAngle  = 0,
4
    distance   = 30,
5
    radius     = 50,
6
    padding    = 100,
7
    orbit      = 200,
8
    offset     = orbit + padding,
9
    orbits     = two.makeGroup();

Incrementaremos earthAngle y moonAngle para hacer que nuestro planeta y la luna rodeen sus órbitas. La variable distance es qué tan lejos estará nuestra luna de nuestra tierra. El radius es el radio de nuestro planeta tierra y el padding es la cantidad de espacio que tendrá nuestro planeta fuera de su órbita. Dicha órbita proviene de la variable orbit. La variable offset es la distancia a la que se desplazará nuestro planeta desde el borde del lienzo. Finalmente, el grupo orbits mantendrá los dos círculos de órbita, lo que nos permitirá mostrarlos u ocultarlos a voluntad. No te preocupes si estás un poco confundido; Verás cómo funcionan todos juntos en un segundo.

Comenzaremos con la línea de órbita de la tierra. Por supuesto, eso es sólo un círculo simple:

1
2
var earthOrbit = two.makeCircle(offset, offset, orbit);
3
earthOrbit.noFill();
4
earthOrbit.linewidth = 4;
5
earthOrbit.stroke = "#ccc";
6
orbits.add(earthOrbit);
7
8
two.update();

Aquí no hay nada nuevo. Esto es lo que deberías ver:

Entonces, necesitamos crear un planeta y colocarlo en su órbita. Primero, necesitamos un medio para averiguar dónde se debe colocar el planeta en la órbita; Y, por supuesto, esto debe cambiar para cada fotograma de animación. Entonces, vamos a crear una función que devolverá las coordenadas x e y del centro para la órbita en función del ángulo actual para posicionarse alrededor del círculo y el radio de la órbita:

1
2
function getPositions(angle, orbit) {
3
    return {
4
        x: Math.cos(angle * Math.PI / 180) * orbit,
5
        y: Math.sin(angle * Math.PI / 180) * orbit
6
    };
7
}

Sí, es un poco de trigonometría, pero no te preocupes demasiado: básicamente, estamos convirtiendo el ángulo (que es un grado) en un radián, usando los métodos de seno y coseno de JavaScript, y luego lo multiplicamos por orbit. Ahora, podemos usar esta función para agregar la tierra a la imagen:

1
2
var pos = getPositions(earthAngle++, orbit),
3
    earth = two.makeCircle(pos.x + offset, pos.y + offset, radius);
4
5
earth.stroke = "#123456";
6
earth.linewidth = 4;
7
earth.fill = "#194878";

Comenzamos por obtener la posición para el primer earthAngle (valor de 0, ¿recuerdas?); luego, hacemos nuestra earth basada en esas posiciones (más el desplazamiento) y la coloreamos. Esto es lo que terminamos con:

Ahora, animemos este planeta. El código de enlace de evento en realidad proviene directamente de Backbone, por lo que puede parecer familiar:

1
2
two.bind("update", function (frameCount) {
3
    var pos = getPositions(earthAngle++, orbit);
4
    earth.translation.x = pos.x + offset;
5
    earth.translation.y = pos.y + offset;
6
});
7
8
two.play();

Lo que sucede aquí es que cada vez que se produce el evento update, estamos utilizando la función getPositions para calcular la posición para el próximo ángulo en la tierra. Entonces, solo tenemos que establecer el centro de la tierra en esas nuevas posiciones, más el desplazamiento. Finalmente, llamamos two.play() para iniciar los eventos de actualización. Si recargas la página ahora, deberías ver la tierra girando alrededor de la órbita.

Buen trabajo hasta ahora, ¿eh? Ahora, ¿qué hay de la luna y su trayectoria de órbita; esto irá por encima de la instrucción bind.

1
2
var moonOrbit = two.makeCircle(earth.translation.x, earth.translation.y, radius + distance);
3
moonOrbit.noFill();
4
moonOrbit.linewidth = 4;
5
moonOrbit.stroke = "#ccc";
6
orbits.add(moonOrbit);
7
8
var pos = getPositions(moonAngle, radius + distance), 
9
    moon = two.makeCircle(earth.translation.x + pos.x, earth.translation.y + pos.y, radius / 4);
10
11
moonAngle += 5;
12
moon.fill = "#474747";

Esto se parece mucho al código del planeta: centramos el círculo de la órbita de la luna en el centro de la tierra usando sus propiedades translation; su radio es el radio de la tierra más la distancia que la luna debe estar alejada de la tierra. Nuevamente, agregamos moonOrbit al grupo orbits.

A continuación, creamos la luna, obteniendo primero la posición deseada y creando un círculo en esa ubicación. Para un radio, usaremos un cuarto del radio que usamos para la Tierra. Estaremos incrementando el ángulo de la luna en 5 cada vez, por lo que se moverá más rápido que la Tierra.

Al desactivar la animación (al comentar la declaración two.bind), obtenemos esto:

Último paso: consigue que la luna se anime. Dentro de esa misma sentencia two.bind, agregue estas líneas:

1
2
var moonPos = getPositions(moonAngle, radius + distance);
3
moon.translation.x = earth.translation.x + moonPos.x;
4
moon.translation.y = earth.translation.y + moonPos.y;
5
moonAngle += 5;
6
7
moonOrbit.translation.x = earth.translation.x;
8
moonOrbit.translation.y = earth.translation.y;

Al igual que antes, obtenemos la nueva posición para la luna y la posicionamos en relación con la tierra. Luego, también movemos el anillo orbital de la luna para que permanezca centrado en la tierra.

Con todo esto en su lugar, nuestro pequeño ejemplo está completo: Aquí hay una instantánea de la acción:

Como dije, también podemos ocultar las órbitas. Ya que ambos están en el grupo orbits, podemos usar la propiedad visible del grupo:

1
2
orbits.visible = false;

Y ahora:


Conclusión

Bueno, eso es un resumen de este tutorial. ¿Crees que estarás usando two.js en alguno de tus propios proyectos? ¿O tal vez tienes una mejor alternativa? ¡Vamos a escucharlo en los comentarios!