Background animado de copos de nieve

Por Nestor Plasencia Prado
Background animado de copos de nieve

En este tutorial aprenderemos a crear un fondo animado de copos de nieve, con las herramientas gráficas del elemento canvas de HTML5.

¡Hola, amigos de DevCode! Hoy aprenderemos a crear un fondo animado de copos de nieve haciendo uso del elemento canvas de HTML5.

En HTML creamos un elemento canvas, solo con un id con la palabra "snow".

En CSS cambiamos el color de fondo del body y el documento html.

html, body {
  background: #2c3e50;
}

Y al elemento canvas, con la propiedad position y con el valor de absolute, extraemos al elemento del flujo del documento y lo ubicamos en la esquina del documento con las propiedades top y left con valores de 0.

canvas {
  position: absolute;
  top: 0;
  left: 0;
}

Ahora solo nos enfocamos en la programación con JavaScript, primero hacemos el llamado del elemento canvas con el método getElementById() y seleccionamos el contexto de 2 dimensiones.

var canvas = document.getElementById('snow');
var ctx = canvas.getContext('2d');

Necesito que el elemento canvas ocupe todo el espacio disponible en el documento, para asignarle sus atributos de ancho y alto utilizo las propiedades del objeto window, que corresponden al alto y ancho de la pantalla.

var w = canvas.width = window.innerWidth;
var h = canvas.height = window.innerHeight;

Tenemos que inicializar 3 variables las cuales almacenan, el número de copos, el tamaño máximo de los copos y array vacío que contendrá cada uno de los elementos que representan los copos respectivamente.

var num = 150;
var tamaño = 3;
var elementos = [];

Ahora declaramos la función inicio, en la cual se genera la posición espacial y el tamaño de cada uno de los elementos. Nos ayudaremos en el método random del objeto Math que proporciona un número aleatorio entre 0 y 1. Las posiciones x e y están entre 0 y el alto y ancho de la ventana respectivamente. El tamaño está entre 0 y el tamaño máximo declarado anteriormente.

inicio();
function inicio() {
  for (var i = 0; i < num; i++) {
    elementos[i] = {
      x: Math.ceil(Math.random() * w),
      y: Math.ceil(Math.random() * h),
      tamaño: Math.random() * tamaño
    }
  }
}

Ahora graficamos cada uno de los elementos en una función que llamaremos nevada.

nevada();

En esta función, primeramente limpiamos el lienzo con el método clearRect(), e iniciamos el trazado de los elementos con beginPath() que los hará independientes unos de otros. Para cada uno de los elementos graficamos un círculo con centro en la posición x e y de los elementos y con un radio del tamaño de dicho elemento. A la propiedad fillStyle le asignamos el color blanco que representa a la nieve.

function nevada() {
  ctx.clearRect(0, 0, w, h);
  for (var i = 0; i < num; i++) {
    var e = elementos[i];
    ctx.beginPath();
    ctx.fillStyle = "white";
    ctx.arc(e.x, e.y,e.tamaño,0,2*Math.PI);
    ctx.fill();
  }
}

Con esto ya tenemos un fondo estático de nieve, puedes aumentar el tamaño e incrementar el número de elementos.


Muchas veces se estila representar a los copos de nieve de una forma geométrica hexagonal, por ello vamos a crear esta representación agregando seis líneas al círculo central del copo. Este copo tiene que adaptarse a los diferentes tamaños que se le asigne por cada elemento, es por ello que toda su representación estará basada en el punto central y en el tamaño que se le asigne.

En el siguiente apéndice utilizaremos las medidas de ancho y alto del canvas. 

var long = w;
var cx = w / 2;
var cy = h / 2;

Definimos las propiedades fillStyle para el color del círculo blanco y strokeStyle para las líneas también de color blanco.

ctx.fillStyle = "white";
ctx.strokeStyle = "white";

Ahora graficamos el círculo con centro en las posiciones de entrada y con radio proporcional al tamaño de entrada.

ctx.arc(cx, cy, long / 15, 0, 2 * Math.PI);

Ahora haremos un bucle para las 6 líneas del copo, dentro del bucle lo primero es mover el pincel al centro del copo con la propiedad moveTo(). Asignamos un grosor a la línea proporcional al tamaño de entrada y por último graficamos la línea desde el centro a cada uno de los extremos, los cuales son calculados mediante el uso de las funciones matemáticas seno y coseno.

for (i = 0; i < 6; i++) {
  ctx.moveTo(cx, cy);
  ctx.lineWidth = long / 20;
  ctx.lineTo(cx + long / 2 * Math.sin(i * 60 / 180 * Math.PI),
            cy + long / 2 * Math.cos(i * 60 / 180 * Math.PI));
}

Para finalizar hacemos el pintado y trazado con los métodos fill() y stroke().

ctx.fill();
ctx.stroke();

Ahora agregamos el apéndice anterior al proyecto general y en lugar de simplemente crear un simple círculo llamaremos a la función cristal.

ctx.beginPath();
cristal(e.x, e.y, e.tamaño);
function cristal(cx, cy, long) {
  ctx.fillStyle = "white";
  ctx.lineWidth = long / 20;
  ctx.arc(cx, cy, long / 15, 0, 2 * Math.PI);
  for (i = 0; i < 6; i++) {
    ctx.moveTo(cx, cy);
    ctx.strokeStyle = "white";
    ctx.lineTo(cx + long / 2 * Math.sin(i * 60 / 180 * Math.PI),
               cy + long / 2 * Math.cos(i * 60 / 180 * Math.PI));
  }
  ctx.fill();
  ctx.stroke();
}

Como ahora los copos necesita mayor espacio, disminuimos el número de elementos y aumentamos el tamaño máximo de estos.

var num = 60;
var tamaño = 40;

Ahora tenemos un fondo estático con copos de nieve geométricos.


Ahora necesito animarlos, generar un movimiento aleatorio, para ello agregamos dos propiedades extras al array de objetos elementos, estas propiedades representan los vectores directrices. Nuevamente con el uso del método random, generamos un número entre 1 y 6 en eje Y, y entre -5 y 5 en el eje X. La explicación de estos valores es que la nieve puede caer hacia la izquierda o derecha, pero solo bajara mas no subirá por ello no puede haber valores negativos en el vector directriz Y.

toX: Math.random() * 10 - 5
toY: Math.random() * 5 + 1

Para mover los copos de nieve simplemente tenemos que iterar cada cierto tiempo y en cada iteración aumentar el valor de su vector directriz. 

Agregamos este aumento luego de graficar cada elemento.

e.x = e.x + e.toX;
e.y = e.y + e.toY;

Y la iteración la realizamos con la función setTimeout() que será llamada, luego de graficar todos los elementos. Esta iteración será cada 10 milisegundos y hará un llamado a función nevada.

timer = setTimeout(nevada,10);

Ahora los copos de nieve han tomado movimiento.


(Si no ves nada, presiona el botón RERUN)

Pero tenemos un problema, los copos siguen cayendo y desaparecen, entonces recordemos a pacman que desaparece en la izquierda y aparece a la derecha o desaparece abajo pero aparece arriba, este comportamiento lo logramos con simples condicionales.

if (e.x > w) { e.x = 0;}
if (e.x < 0) { e.x = w;}
if (e.y > h) { e.y = 0;}

Si desaparece en la izquierda aparece en la derecha y viceversa, además de si desaparece abajo que aparece arriba.

Otro inconveniente que se puede presentar es que al redimensionar la pantalla el elemento canvas no lo haga, por ellos agregamos este evento y actualice dichas medidas.

window.addEventListener("resize", function() {
  w = canvas.width = window.innerWidth;
  h = canvas.height = window.innerHeight;
});

¡Ya esta! Tenemos nuestro fondo animado de copos de nieve.


Si te estás preguntando cómo utilizar lo que acabas de aprender, te dejo dos ejemplos en el primero utilizaremos la animación para un header y en el segundo para un background.

Ejemplo 1


Ejemplo 2


Si has llegado hasta aquí, estoy seguro que deseas aprender mucho mas de JavaScript, recuerda que en DevCode ya están abiertas las inscripciones para el Curso de Fundamentos de JavaScript. Además, tenemos una promoción especial por navidad con la que podrás obtener acceso con un 45% de descuento y un polo de regalo.

Si te gustó este tutorial, no olvides compartirlo con tus amigos y dejarnos tus dudas u opiniones en la caja de comentarios. ¡Hasta la próxima!

¿Te gustó el tutorial?

Ayúdanos a llegar a más personas

Nestor Plasencia Prado

Nestor Plasencia Prado

Programmer | Developer | Maker | Robot Designer | Knowmad