Animar particulas

facebook-svg gplus-svg twitter-svg

Que queremos conseguir

Queremos crear una animación de círculos que flotan al azar y rebotan en los bordes del canvas.

See the Pen Particulas #2 by Gabi (@enxaneta) on CodePen.

Crear partículas en canvas

En el HTML tenemos un elemento canvas cuyo id="c", el lienzo donde queremos pintar las partículas.

<canvas id='c'></canvas>

En el JavaScript primero iniciamos el canvas:

var c = document.getElementById("c");
var ctx = c.getContext("2d");
var cw = c.width = window.innerWidth;
var ch = c.height = window.innerHeight;

Las variables cw y ch representan, como es fácil de ver, la anchura y respectivamente la altura del canvas. Utilizamos window.innerWidth y window.innerHeight porque queremos que el canvas cubra toda la ventana.

Cuando trabajamos con partículas en canvas es importante crear un array donde guardar todas las características de cada partícula en parte. Para esto en el JavaScript creamos una función (Item()) que  construye una a una las partículas. En este caso las partículas son círculos de varios tamaños, repartidos aleatoriamente en el canvas.

La función Item()

Primero construimos una función que nos ayude encontrar un número aleatorio entre dos valores minim y maxim.

function randomIntFromInterval(minim, maxim) {
    return ~~(Math.random() * (maxim - minim + 1) + minim);
  }

Para quien no lo sepa, en JavaScript, la doble tilde (~~) es un operador equivalente a Math.floor() o casi.

Ahora podemos escribir la función Item ( un constructor ).

function Item() {
	this.r = randomIntFromInterval(10, 40);
	this.x = randomIntFromInterval(this.r, cw - this.r);
	this.y = randomIntFromInterval(this.r, ch - this.r);
	this.color = colores[~~(Math.random() * colores.length)];
}

En este caso la función Item decide el valor del radio (this.r) de cada partícula como un número aleatorio entre 10 y 40;

this.r = randomIntFromInterval(10, 40);

También establece las coordenadas en x e y de cada partícula: un punto del canvas escogido al azar.

this.x = randomIntFromInterval(this.r, cw - this.r);
this.y = randomIntFromInterval(this.r, ch - this.r);

donde cw y ch representan la anchura y respectivamente la altura del lienzo.

Por último establece el color (this.color) de cada partícula.  En este caso tenemos un array con la paleta de colores que queremos utilizar.

var colores = ["#FA2E59", "#FF703F", "#FF703F", "#F7BC05", "#ECF6BB", "#76BCAD"];

La función Item() escoge un  color al azar de esta paleta:

 
this.color = colores[~~(Math.random() * colores.length)];

Popular el array de partículas

Primero establecemos algunas variables necesarias:
el número de partículas: var Num = 50; y
el array de las partículas: var particulas = [];

A continuación creamos una a una las partículas: var p = new Item(); y las añadimos al final del array utilizando el método push: particulas.push(p).

for( var i = 0; i < Num; i++){
     var p = new Item();
     particulas.push(p);
     dibujarItem(p);
}

Finalmente dibujamos uno a uno los ítems utilizando la función dibujarItem(Item) que tenemos que escribir.

La función dibujarItem(Item)

Para dibujar los ítems utilizamos una función nada complicada: dibuja un circulo: ctx.arc( Item.x, Item.y, Item.r, 0, 2*Math.PI); y lo llena de color: ctx.fillStyle = Item.color; ctx.fill();

function dibujarItem(Item){
         ctx.beginPath();
         ctx.arc(Item.x,Item.y,Item.r,0,2*Math.PI);
         ctx.fillStyle = Item.color;
         ctx.fill();
  }

Y ya está. . . o casi.

Hay que adaptarse

Al inicio hemos decidido que queremos que el canvas ocupe toda la ventana.  En este caso habrá que tener en cuenta  los diferentes tamaños de pantalla que hay por allí. También es posible que el usuario quiera redimensionar la ventana, y entonces nos podemos quedar con menos partículas a la vista, o con trozos de pantalla vacía. I esto no puede ser. Para solucionar  este problema vamos a escribir una función de inicialización: Init() que establece el nuevo tamaño del canvas "onresize" y vuelve a popular el array de partículas. Además  - por razones estéticas – utilizamos ctx.globalCompositeOperation = "difference", que marcará con un color diferente las zonas de intersección entre partículas.

Lea más acerca de los modos de fusión en canvas.

function Init() {
    // establece el nuevo tamaño del canvas
    cw = c.width = window.innerWidth;
    ch = c.height = window.innerHeight;
    //vacia el array de las particulas 
    particulas.length = 0;
    //vuelve a popular el array de particulas
    for (var i = 0; i < Num; i++) {
      var p = new Item();
      particulas.push(p);
      dibujarItem(p);
      ctx.globalCompositeOperation = "difference";
    }
}

Llamamos una vez la función Init() una vez, al cargárse la página:

window.addEventListener('load', Init, false);

Ulteriormente el método setTimeout verifica cada 15 milisegundos si la página se ha redimensionado, y en este caso llama de nuevo la función Init.

window.addEventListener('load', Init, false);
  setTimeout(function() {
      window.addEventListener('resize', Init, false);
  }, 15);

See the Pen Particulas #1 by Gabi (@enxaneta) on CodePen.

Esto está muy bien, pero lo podemos mejorar animando las partículas que acabamos de dibujar.

Animar partículas en canvas

Para animar las partículas en el canvas escribimos la función animarItem(Item), que toma como argumento el objeto Item (el que contiene el valor del radio, de las coordenadas y  el color  de la particula.)

La función animarItem(Item)

La función animarItem(Item) actualiza con cada reiteración las coordenadas en x e y de la partícula. Esencialmente lo que dice es que si la partícula  llega al borde del canvas, tiene que cambiar de dirección.

if (Item.x >= cw - Item.r || Item.x <= Item.r) {
        Item.desplX = -1 * Item.desplX;
}
if (Item.y >= ch - Item.r || Item.y <= Item.r) {
        Item.desplY = -1 * Item.desplY;
}

De lo contrario la partícula avanza un paso (desplX, desplY) más en la misma dirección.

Item.y += Item.desplY;
Item.x += Item.desplX;

Al final llama la función dibujarItem(), que...  dibuja la partícula y el modo de fusión ctx.globalCompositeOperation = "difference";

function animarItem(Item){
  // actualiza las coordenadas en x e y de cada Item
  if (Item.x >= cw - Item.r || Item.x <= Item.r) {
        Item.desplX = -1 * Item.desplX;
      }
  if (Item.y >= ch - Item.r || Item.y <= Item.r) {
        Item.desplY = -1 * Item.desplY;
      }
  Item.y += Item.desplY;
  Item.x += Item.desplX;
              
  dibujarItem(Item);
  ctx.globalCompositeOperation = "difference";
}

La función Animación()

El siguiente paso es escribir la función que genera la animación de todas la partículas:

function Animacion(){
  //  limpia el canvas
  ctx.clearRect(0,0,cw,ch);
  // para cada particula llama la función animarItem
  for( var i = 0; i < particulas.length; i++){
              animarItem(particulas[i]);
}
  // la función Animacion vuelve a llamarse a si misma de manera recurrente
  requestId = window.requestAnimationFrame(Animacion);     
}

Lea más acerca del método requestAnimationFrame() y cómo utilizarlo para generar animaciones en canvas.

Actualizar la función Init()

Necesitamos hacer un pequeño cambio a la función Init(). Borramos las últimas dos líneas de código, ya que aparecen incorporadas en la función que genera la animación, y por último llamamos a la función Animacion().

function Init(){
      cw = c.width = window.innerWidth, cx = cw/2;
      ch = c.height = window.innerHeight, cy = ch;
              
      particulas.length = 0;
      for( var i = 0; i < Num; i++){
           var p = new Item();
           particulas.push(p);
           dibujarItem(p);
           ctx.globalCompositeOperation = "difference";
  }          
  Animacion();
  }

See the Pen Particulas #2 by Gabi (@enxaneta) on CodePen.