RSS Feed

Fernet Volador: detrás de escena

0

junio 25, 2012 by - @pjnovas

A pedido del público (mentira, lo queria compartir :P), el código del Fernet Volador explicado:

Antes que nada, si no lo viste en funcionamiento, abri tu consola y pone konamiFernetJS.run(), para terminarlo ingresa konamiFernetJS.stop(). Todo nace desde este post de Matias

El objetivo del fernet volador es basicamente lo que se vé, una botella de fernet con una capita y que pudieramos volar por la pantalla.

Un poco de como armarlo

Primero nuestra imagen de la botella, hacemos los movimientos de la capa en la misma imagen, la cual va a estar cambiando continuamente (el background-position en CSS) dando el efecto de que se mueve la capa:

Vamos a tener que mover la botella en angulos dependiendo de hacia donde este yendo, por lo que usamos CSS3 y el copado rotate, pero también vamos a tener que mover la posición de la botella en la pantalla para simular el movimiento, asi que armamos 2 divs, uno con la imagen del fernet (en posición absoluta), en el cual vamos a ir modificando el rotate (verde) y otro que contiene a este con posicion absoluta al documento (rojo), al cual le vamos a cambiar el top y left para ir posicionandolo.

Y todo esto va a depender de las flechas presionadas, asi que diría de mantenerlas en un array para que el loop se encargue de verificar cual está y aplicar los CSS.

Estructurando el codigo

Vamos a estructurar nuestro código armando una función para iniciar, 2 funciones para cuando presiona una flecha en el teclado y cuando la suelta, una función para mover la botella (la que va a estar en un loop) y por último una función para limpiar el dom cuando termina.

Principalmente el fernet volador tiene 2 entradas públicas, una para iniciar y otra para terminar, asi que vamos a dejar solo eso público utilizando lo que vimos en este post

Arranquemos por el objeto konamiFernetJS:

var konamiFernetJS = (function($){
  //variables privadas
  var running = false, //si está en ejecución
    timerMove, //timer para movimiento
    timerCapa, //timer para la capa
    bottleWrap, // div de posicion para la botella
    bottle, //div de imagen y rotacion de la botella
    legend, //leyenda inicial
    vel = 10, //velocidad
    cls = 0, //angulo a aplicar
    $window = $(window), //el jquery wrapper del window 
    keys = [], // array con las flechas presionadas
    key = { //keyCodes de las flechas del teclado
      up: 38,
      down: 40,
      left: 37,
      right: 39
    };

  //function para la animacion de la botella
  //se va a llamar en un loop cada x segundos
  var moveBottle = function(){};  

  //eventos de keyup y keydown
  var konamiKeyDown = function(e){};
  var konamiKeyUp = function(e){};

  //funcion para iniciar dom, eventos y loops
  var runKonami = function(){};

  //funcion para  limpiar todo, dom, eventos y detener loops
  var reset = function(){};
 
  //acceso publico para iniciar y detener
  return {
    run: function(){
      if (!running){
        running = true;
        runKonami();
      }
    },
    stop: reset
  }; 
})(jQuery);

De esta manera dejamos toda funcionalidad, variables, etc. dentro de una sola variable global konamiFernetJS.

iniciando: runKonami()

En esta funcion vamos a crear el dom, eventos e iniciar los loops de animacion:

var runKonami = function(){
  //creamos los elementos del DOM
  //este es el div que rota con la imagen de la botella
  bottle = $("<div>").addClass('fernet-capita');
  //mensaje inicial 
  legend = $("<div>").addClass('legend').text("<- Fernet volador");

  //este va a ser el div que se mueve por la pantalla
  bottleWrap = $("<div>").addClass("bottle-wrap")
    .append(bottle) //agregamos la botella
    .append(legend) //agregamos el mensaje inicial
    //posicionamiento inicial de la botella
    .css('left', ($window.width()/3) + 'px')
    .css('top', (($window.height()/2) + $window.scrollTop()) + 'px')
    .appendTo('body'); //agregamos todo al body

  //hacemos desaparecer el scroll de la pagina, ya que lo vamos a manejar nosotros		
  $('body, html').css('overflow', 'hidden');

  //creamos una funcion para el array keys, simplificando el manejo de las flechas presionadas
  //vamos a usar mucho esta funcion dentro de moveBottle
  //el array keys es donde vamos a mantener las flechas presionadas
  keys.has = function(){
    for(var i=0;i<arguments.length;i++){
      if(keys.indexOf(arguments[i]) === -1) return false;
    }
    return true;
  };
  
  //bindeamos los eventos keyup y keydown del window a nuestras funciones
  $(document).bind('keydown', konamiKeyDown);
  $(document).bind('keyup', konamiKeyUp);

  //limpiamos todo interval que este dando vueltas (por si las dudas)
  clearInterval(timerMove);
  clearInterval(timerCapa);
  
  //creamos el loop para el movimiento de la botella
  //es decir, cada 50 milisegundos se va a llamar a la función moveBottle
  timerMove = setInterval(moveBottle, 50);

  //creamos el loop para el movimiento de la capa negra de la botella	
  //es el cambio continuo de la imagen, para animar la capa (background-position)	
  var toggle = false;
  timerCapa = setInterval(function(){
    toggle = !toggle;
    if (toggle) bottle.addClass('x');
    else bottle.removeClass('x');
  }, 200);
		
};

eventos keyup y keydown

Manejando el array keys dependiendo de que esta presionado y que se dejó de presionar

var konamiKeyDown = function(e){
  //comprobamos que la flecha presionada no esté en el array (sea nueva)
  if (keys.indexOf(e.which) === -1) {
    if (keys.length > 1) keys.shift(); // si el array ya tiene 2, sacamos la primera
    keys.push(e.which); //agregamos la flecha al array
  }

  //si todavia esta la leyenda, animamos para que se vaya
  if (legend){
    legend.animate({opacity: 0}, 1000, function(){
      legend.remove();
      legend = null;
    });
  }
};
	
var konamiKeyUp = function(e){
  var idx = keys.indexOf(e.which);
  if (idx !== -1)
    keys.splice(idx, 1); //si el array tiene la flecha que soltamos, la eliminamos
};

loop de movimiento de la botella: moveBottle()

var moveBottle = function(){
  //tomamos posiciones actuales y la altura de la ventana actual con su scroll
  var top = bottleWrap.position().top, 
    left = bottleWrap.position().left,
    half = $window.height()/2,
    hScroll = $window.scrollTop() + $window.height()/2;

  //comprobamos que flechas están en el array y asignamos angulo y posicion
  if(keys.has(key.up)){
    cls = 0;
    if(top > 0) top-=vel; 
  }
  if(keys.has(key.right)){
    cls = 90;
    if((left + bottleWrap.width()) < $window.width()) left+=vel;
  }
  if(keys.has(key.down)){
    cls = 180;
    if((top + bottleWrap.height() * 2) < $window.height() + $window.scrollTop()) top+=vel;
  }
  if(keys.has(key.left)){
    cls = 270;
    if(left > 0) left-=vel;
  }

  //este caso es para cuando tiene 2 flechas presionadas
  if(keys.length > 1){
    cls = 45;
			
    if (keys.has(key.up, key.right)) cls *= 1;
    else if (keys.has(key.right, key.down)) cls *= 3;
    else if (keys.has(key.down, key.left)) cls *= 5;
    else if (keys.has(key.left, key.up)) cls *= 7;
  }

  //creamos el CSS con la propiedad rotate
  var style = "transform:rotate([d]deg);"
    + "-ms-transform:rotate([d]deg);"
    + "-moz-transform:rotate([d]deg);"
    + "-webkit-transform:rotate([d]deg);"
    + "-o-transform:rotate([d]deg);";
					
  //reemplazamos [d] por el angulo calculado			
  style = style.replace(/\[d\]/g, cls); 

  //comprobamos si hay que mover el scroll de la ventana
  if(top > hScroll || top < hScroll){
    $window.scrollTop(top - half);
  }

  //asignamos la posicion nueva y el CSS rotate
  bottleWrap.css('top', top + 'px').css('left', left + 'px');
  bottle.attr('style', style);
};

Limpiamos memoria cuando se detenga: reset()

var reset = function(){
  //eliminamos eventos
  $(document).unbind('keydown', konamiKeyDown);
  $(document).unbind('keyup', konamiKeyUp);

  //volvemos el scroll a como estaba
  $('body, html').css('overflow', 'auto');

  //detenemos los loops
  clearInterval(timerMove);
  clearInterval(timerCapa);

  bottleWrap.empty().remove(); //eliminamos el DOM
  keys = []; //volvemos a cero el array de flechas
  running = false; //le avisamos que ya no está en ejecución
};

Y eso es todo el fernet volador (sacando el css), estoy seguro que cambiarias varias cosas (con escribir el post yo cambiaría algunas :P) por eso te dejo un repo en github para que tengas todo el ejemplo completo y puedas modificarlo o reutilizarlo.

Fernet Volador en GitHub