AngularJS: animaciones con scroll

No tenía muy claro cómo titular esta entrada, dado que su título original es AngularJS: Scroll Animations. Como en ocasiones anteriores se trata de una “traducción-no-literal”/explicación de un artículo tutorial en inglés.

En fin, ¿a qué nos referimos con esto de animaciones con scroll? Seguro que habéis visto más de una página que las tiene. Se trata de animaciones que se activan conforme vas haciendo scroll en la página, al alcanzar una determinada posición. La web Let’s Free Congress es presentada con ejemplo de esto en el texto original.

AngularJS logo

La idea es que la animación comience cuando el usuario llegue a la parte de la pantalla, para lo cual nos serviremos de la directiva scrollPosition, que se nos presenta así en el ejemplo original

.directive('scrollPosition', ['$window', '$timeout', '$parse', function($window, $timeout, $parse) {
    return function(scope, element, attrs) {

        var windowEl = angular.element($window)[0];
        var directionMap = {
          "up": 1,
          "down": -1,
          "left": 1,
          "right": -1
        };

        // Recuperamos el elemento con el scroll
        scope.element = angular.element(element)[0];

        //Almacenamos los elementos que escuchan a este evento
        windowEl._elementsList = $window._elementsList || [];
        windowEl._elementsList.push({element: scope.element, scope: scope, attrs: attrs});

        var element, direction, index, model, scrollAnimationFunction, tmpYOffset = 0, tmpXOffset = 0;
        var userViewportOffset = 200;

        function triggerScrollFunctions() {

          for (var i = windowEl._elementsList.length - 1; i >= 0; i--) {
            element = windowEl._elementsList[i].element;
            if(!element.firedAnimation) {
              directionY = tmpYOffset - windowEl.pageYOffset > 0 ? "up" : "down";
              directionX = tmpXOffset - windowEl.pageXOffset > 0 ? "left" : "right";
              tmpXOffset = windowEl.pageXOffset;  
              tmpYOffset = windowEl.pageYOffset;  
              if(element.offsetTop - userViewportOffset < windowEl.pageYOffset && element.offsetHeight > (windowEl.pageYOffset - element.offsetTop)) {
                model = $parse(windowEl._elementsList[i].attrs.scrollAnimation)
                scrollAnimationFunction = model(windowEl._elementsList[i].scope)
                windowEl._elementsList[i].scope.$apply(function() {
                  element.firedAnimation = scrollAnimationFunction(directionMap[directionX]);  
                })
                if(element.firedAnimation) {
                  windowEl._elementsList.splice(i, 1);
                }
              }
            } else {
              index = windowEl._elementsList.indexOf(element); //TODO: Add indexOf polyfill for IE9 
              if(index > 0) windowEl._elementsList.splice(index, 1);
            }
          };
        };
        windowEl.onscroll = triggerScrollFunctions;
      };   
    }]);

Tal directiva se ha utilizado en este ejemplo. En ella podéis ver como la barra de la pantalla del móvil progresa según bajáis la barra.

En fin, la cosa es explicar ahora como usar esto. Como en el ejemplo original, iremos desgranando poco a poco el código, despedazándolo para ver cómo funciona.

Lo principal es añadir la directiva al elemento que queremos animar:

.row.show-for-large-up
  .teaser(scroll-position, scroll-animation='fireupApplicationDesignAnimation')
    .row
      .large-6.columns.left-align.margin-top
        h1(data-i18n="_CareerDesign_APPLICATIONDESIGNTITLE")

Esto le dice a AngularJS que cuando el usuario llegue al área determinada del DOM debe dispararse la animación. En vuestro controlador deberíais tener algo así

$scope.fireupApplicationDesignAnimation = function(scrollDirection) {
        scrollDirection > 0 ? reduceAmount() : aumentAmount(); // We want to increase on scrollDown
        setOffsetForImage();
    };

La directiva envía al controlador la dirección del scroll, lo que permite mostrar animaciones basadas en él. En ocasiones querrás ejecutar la animación sólo una vez en lugar de que ocurra cada vez que el scroll esté a su altura. Puedes lograrlo devolviendo un valor true y chequeándolo antes de lanzar la animación, como en este ejemplo:

$scope.fireupMarketingDesignAnimation = function() {
      if(!firedMarketingAnimation) {
        window.animations.marketingAnimation.init();
        firedMarketingAnimation = true;
        return firedMarketingAnimation;  
      }
    }

Como podéis ver en el ejemplo la función que lanza la animación está dentro de un if que hará que sólo se ejecute una vez.

En fin, resumiendo ¿Cómo funciona la directiva?. El proceso es el siguiente:

  • Crea un array de elementos que requieren un eventListener del tipo onScroll. Puedes tener varios almacenados, y si devuelves un true para una sola ejecución serán eliminados para reducir el uso de memoria.
  • Añade un eventListener onScroll que comprueba en qué parte de la página está el usuario.
  • Recorre tus elementos vinculados con la directiva comprobando si se han activado las animaciones.
  • Si no lo han hecho y el usuario se posiciona junto a ellas con el scroll entonces se activan. Esto obliga a hacer mucho uso de $parse, así que léete bien su documentación para entenderlo al 100%.
  • Si la animación llamada a través de scope devuelve true, la extrae del array, como dijimos en el primer punto.

La conclusión:

La directiva es útil cuando necesitas lanzar varias animaciones en un periodo de tiempo específico. También te permitirá definir un comportamiento específico según la posición del scroll o hasta para realizar cambios en propiedades CSS3 relacionándolas con la posición de la página.

En este ejemplo en Codepen, creado por el autor del artículo original, podéis ver cómo también funciona en horizontal. Si movéis el scroll lentamente veréis como el balón lo sigue. Eso sí, probad con Chrome porque a mi en Firefox en lugar de una pelota se me ve una especie de mojón raro.

En fin, finalmente recomendar el blog de jjperezaguinaga Surviving by Coding, donde podréis encontrar mucha info sobre AngularJS y otros temas de desarrollo web, en inglés.

CSS3 Box Shadow: sombras sin usar imágenes

Seguimos otra vez con truquillos de CSS3 para vuestra web. Mientras me desquicio usando un teclado de silicona para escribir (cosa incómoda donde las haya) vamos a comentar el uso de la propiedad box-shadow de CSS3. Como siempre, ojo con IE que es puñetero cuando se trata de trabajar con stándares.

En su uso básico box-shadow necesita al menos tres parámetros, si bien acepta hasta cinco.

  • Desplazamiento horizontal: Define la posición horizontal de la sombra respecto al elemento al que se la aplicamos. Si le enviamos un valor positivo la sombra se situará a la derecha del elemento. Si el valor es negativo se situará a la izquierda.
  • Desplazamiento vertical: Define la posición vertical de la sombra. Si recibe un valor positivo la sombra se situará debajo del elemento, y con uno negativo encima.
  • Radio de desenfoque: Cuanto mayor sea, más transparente será la sombra. En caso de 0 la sombra será totalmente nítida. Se trata de un valor opcional y 0 es su valor por defecto.
  • Radio de dispersión: Cuanto mayor sea, mayor dispersión tendrá la sombra. Es opcional y el valor por defecto es cero. En ese caso el tamaño será igual al desenfoque.
  • Color: Finalmente el color que tendrá la sombra. Es el tercer valor obigatorio.
  • Inset: Existe un parámetro más, que es este inset. Si lo incluimos al principio hará que la sombra en lugar de estar en el exterior se sitúe en el interior del elemento..

La sintaxis sería como en este ejemplo:

/*Sombra gris abajo derecha*/
.sgad{
    box-shadow: 5px 5px 2px #666;
}

/*Sombra azulada arriba izquierda muy dispersa*/
.saaid{
    box-shadow: -5px -5px 5px 14px #99C;
}

/*Sombra interna gris*/
.sig{
    box-shadow: inset 0 0 10px #666;
}

Si queréis una buena serie de ejemplos y trucos los podéis encontrar en este enlace. Y recordad, ojo con el Explorer.

Zoom sobre texto con CSS3 usando transiciones

Otra nueva entrada sobre las virtudes de CSS3, en este caso vamos a hacer una pequeña introducción a las transiciones creando un código que nos permita que la letra de un párrafo aumente de tamaño cuando el ratón esté sobre ella.

Para ello por un lado tendremos que definir la transición sobre el elemento a modificar y luego la modificación cuando se produzca el evento.

Lo primero sería así:

.aumenta {
    font-size: 1.1em;
    width: 300px;
 
    -moz-transition: font-size 500ms linear 0s; 
    -webkit-transition: font-size 500ms linear 0s;
    -o-transition: font-size 500ms linear 0s;
    transition: font-size 500ms linear 0s;
}

Explicación: tras definir el tamaño del texto para la clase aumenta (1.1 em) definimos la transición. Hay varios métodos para la transición, así que he usado la propiedad shorthand de transition. El primer parámetro (font-size) es para especificar a qué propiedad se aplicará la transición, el segundo el tiempo que tardará, el tercero que se hará siempre a la misma velocidad (linear) y el cuarto que se haga sin delay.

Y ahora que tenemos la transición definida para todos los navegadores (ojo!!! Explorer 9 no soporta todavía transiciones, dice Bill Gates que igual para la 10) toca definir cómo se transformará el texto cuando esté sobrevolado por el ratón:

.aumenta:hover{
    font-size:1.6em;
}

Definimos que en el evento hover la fuente pase a ser más grande. Con esto ya tenemos nuestra transición lista.

Escalar imagen con CSS3 al hacer hover

Bueno, una minientrada rápida:

¿Necesitas que una imagen se agrande cuando pases el cursor por encima (o encoja, que también se puede)? Tradicionalmente la cosa sería hacerlo con javascript, pero ahora sólo con CSS3 es posible. En el siguiente ejemplo verás como hacer que la imagen crezca un 20%:

img:hover{ 
    -webkit-transform: scale(1.2);
    -moz-transform: scale(1.2);
    -o-transform: scale(1.2);
    -ms-transform: scale(1.2);
    transform: scale(1.2)
}

La idea es usar transform scale() y pasarle el tamaño que queremos darle como parámetro. Por cuestiones de compatibilidad hemos añadido los métodos concrectos para webkit, mozilla, opera y explorer. Cuestiones a tener en cuenta: Pues la primera, no pasarse ampliando para que la imagen no pierda definición en exceso. La segunda, ten en cuenta tamaños de márgenes y padding para que al crecer no te desplace el resto de elementos.

Truquito CSS fácil y rápido para este día de carnaval.

Tres consejos para el diseño responsivo

Había dejado un poco tiradas las traducciones de Web Designer Wall tras haber hecho dos sobre diseño responsivo y media queries. Ahora voy con el tercer artículo (que curiosamente era el primero que leí) sobre cómo hacer una página con diseño responsivo en tres cómodos pasos.

El primer paso es desactivar la propiedad que hace que en algunos navegadores móviles el tamaño de la página se reduzca al de la pantalla (se reduzca a saco, sin adaptar nada más, dando como resultado muchas veces páginas inusables):

<meta name="viewport" content="width=device-width, initial-scale=1.0">

Bueno, se me olvidaba comentar que como en todas las entradas de esta web hay una demo. Lo siguiente para el diseño es usar adecuadamente la estructura de HTML5, que en el ejemplo sería:

<div id="pagewrap">
  <header></header>
  <content></content>
  <sidebar></sidebar>
  <footer></footer>
</div>

En un principio la altura del header sería de 180 px, la anchura del content de 600 px y la del sidebar de 300.

Y finalmente toca la hora de las media queries para que el diseño se adapte.

Por ejemplo, para una dispositivo con una resolución máxima de 980px pasamos a tamaños relativos:

@media screen and (max-width:980px){
 #pagewrap{
    width:94%;
 }
 content{
    width:65%;
 }
 sidebar{
    width:30%;
 }
}

Para una de menos de 700px modificamos la hoja de estilos para que se posicione la sidebar debajo del contenido:

@media screen and (max-width:700px){
 content{
    width:auto;
    float: none;
 }
 sidebar{
    width:auto;
    float:none;
 }
}

Y para una pantalla de un móvil (máximo de 480 px) reducimos el tamaño de los títulos, dejamos que el ancho de la cabecera (header) se ajuste automáticamente y directamente quitamos la sidebar:

@media screen and (max-width:480px){
 header{
    height:auto;
 }
 h1{
    font-size:24px;
 }
 sidebar{
    display:none;
 }
}

Concluye el artículo remitiendo al de diseño con media queries que yo también traduje en este blog y referencié antes.

Espero que esta pequeña guía tutorial que he traducido, junto a las otras dos traducciones sobre media queries, os hayan servido de ayuda en la edición de vuestros proyectos web. Y si no… jQueryMobile siempre es una ayuda.

Datepicker fácil en tu web

¿Necesitas un “datepicker”? Con esto quiero decir necesitas introducir un calendario en un formulario web, a día de hoy hay muchos sencillos métodos para hacerlo. Pero estas son las dos más fáciles:

  • Campo date en HTML5: Un campo del formulario definido como de tipo date ya te arregla en gran parte el problema. Tiene el handicap de que cada navegador te saca el calendario con el formato que le da la gana
    <input type="date"/>
  • Calendario datepicker con jQueryUI: Esta opción incluye más opciones de personalización. Lo primero es descargar la librería jQuery y lo siguiente bajar los componentes de jQueryUI necesarios, incluyendo sus estilos. Luego para implantarlo debes cargar las dos librerías en la cabecera del documento, usar un campo input normal (en el ejemplo se llamará #calendario) y un script como el siguiente:
    <script>
    	$(function() {
    		$( "#calendario" ).datepicker({
    			changeMonth: true,
    			changeYear: true
    		});
    	});
    	</script>
    
    <div class="ejemplo">
    
    <p>Date: <input type="text" id="calendario"></p>
    
    </div>
    

En fin, para el caso del jQueryUI tienes muchos más ejemplos en la propia página con las distintas opciones.

Diseño responsivo con media queries de CSS3

Siguiendo con los artículos de Web Designer wall, continuamos con el tema de las media queries de CSS3, en este caso con otra traducción/explicación de uno de sus artículos.

En este artículo comienza repitiendo lo que ya hablamos en la otra entrada sobre media queries, que el diseño responsivo a día de hoy cobra mayor imortancia por la variedad de resoluciones que pueden usar los usuarios, y que por tanto el aspecto tiene que adaptarse a la ventana. El ejemplo del resultado que se busca conseguir es este.

El conceto, como diría Manquiña (el conceto es el conceto), es el siguiente: La página tiene un contenedor de tamaño fijo (980px) optimizado par una resolución mayor de 1024 px. La media query se utiliza para saber si el tamaño es menor de 980px, en cuyo caso se pasa a un diseño fluido. Y si el caso es que es menor de 650px entonces, directamente se convierte en un diseño con una sola columna.

El marcado HTML es el siguiente, donde se ha creado un contenedor pagewrapper que envuelve cabecera, cuerpo y pie:

<div id="pagewrap">

	<header id="header">

		<hgroup>
			<h1 id="site-logo">Demo</h1>
			<h2 id="site-description">Site Description</h2>
		</hgroup>

		<nav>
			<ul id="main-nav">
				<li><a href="#">Home</a></li>
			</ul>
		</nav>

		<form id="searchform">
			<input type="search">
		</form>

	</header>
	
	<div id="content">

		<article class="post">
			blog post
		</article>

	</div>
	
	<aside id="sidebar">

		<section class="widget">
			 widget
		</section>
						
	</aside>

	<footer id="footer">
		footer
	</footer>
	
</div>

Como hemos comentado miles de veces, el marcado HTML5 no se lleva bien con las veriones de IE anteriores al 9, pero puedes solucionarlo introduciendo esta línea en la cabecera, que enlazan con una librería de javascript que subsanará el problema:

<!--[if lt IE 9]>
	<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->

Tras esto toca comenzar con el css, y lo primero es resetar todos los elementos de HTML5 a elementos de bloque:

article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { 
    display: block;
}

Y tras esto toca la estructura principal del css. El contenedor principal ha sido fijado en 980px, el header tiene una altura fija de 160px, el contente un ancho de 600px flotado a la izquierda y el sidebar, flotado a la derecha, de 280px.:

#pagewrap {
	width: 980px;
	margin: 0 auto;
}

#header {
	height: 160px;
}

#content {
	width: 600px;
	float: left;
}

#sidebar {
	width: 280px;
	float: right;
}

#footer {
	clear: both;
}

Todo esto, así de momento, daría lugar a un diseño estático tal que el de esta demo. Ahora toca la parte divertida: CSS3 Media Queries. Como el HTML5, CSS3 tampoco es que furrule muy bien en IE pre-9, así que de nuevo habrá que tirar de una librería de .js para solventar el problema.

<!--[if lt IE 9]>
	<script src="http://css3-mediaqueries-js.googlecode.com/svn/trunk/css3-mediaqueries.js"></script>
<![endif]-->

La idea es hacer un css externo con las media queries, introduciéndolo en el documento tal que así

<link href="media-queries.css" rel="stylesheet" type="text/css">

Luego vendrán las siguientes líneas para cuando la ventana donde visualicemos sea menor de 980px se aplicarán las siguientes reglas:

  • El contenedor principal se resetea al 95%
  • El contenedor de contenido pasa al 60%
  • El contenedor lateral pasa al 30%

El usar el % hace los contenidos fluídos:

@media screen and (max-width: 980px) {

	#pagewrap {
		width: 95%;
	}

	#content {
		width: 60%;
		padding: 3% 4%;
	}

	#sidebar {
		width: 30%;
	}
	#sidebar .widget {
		padding: 8% 7%;
		margin-bottom: 10px;
	}

}

Luego tocará hacer el media query para tamaños menores de 650px, con una única columna:

  • header = resetear la altura a auto
  • searchform = lo mandamos a una posición absoluta, a 5px del borde superior
  • main-nav = se reseta su posición a estatic
  • site-logo = lo mismo
  • site-description = lo mismo también
  • content = devolvemos el ancho a auto
  • sidebar = ponemos el ancho al 100% y quitamos la propiedad de flotado
@media screen and (max-width: 650px) {

	#header {
		height: auto;
	}

	#searchform {
		position: absolute;
		top: 5px;
		right: 0;
	}

	#main-nav {
		position: static;
	}

	#site-logo {
		margin: 15px 100px 5px 0;
		position: static;
	}

	#site-description {
		margin: 0 0 15px;
		position: static;
	}

	#content {
		width: auto;
		float: none;
		margin: 20px 0;
	}

	#sidebar {
		width: 100%;
		float: none;
		margin: 0;
	}

}

Finalmente tenemos la versión para menos de 480px, pensada para el iPhone. Implica evitar el zoom que automáticamente hace Safari sobre el texto y toquetear el menú de navegación principal:

@media screen and (max-width: 480px) {

	html {
		-webkit-text-size-adjust: none;
	}

	#main-nav a {
		font-size: 90%;
		padding: 10px 8px;
	}

}

Ahora toca el truquito para imágenes flexibles, con un pequeño hack para que funcione en IE8 (el width: auto\9; de la línea final):

img {
	max-width: 100%;
	height: auto;
	width: auto\9; /* ie8 */
}

Con los vídeos va igual, menos para Safari, dode max-width no va con vídeos (con fotos sí, extraño). Finalmente, y siguiendo con el iPhone de los huevos, toca metar una meta-tag de escala inicial, ya que el iPhone tiende a modificar las páginas para adaptarlas a la pantalla del dispositivo:

<meta name="viewport" content="width=device-width; initial-scale=1.0">

Y con esto está el tutorial terminado, en el artículo citado podrás encontrar además todo el código y las imágenes de la página de ejemplo. Un placer el haberos traducido esto, y todavía me queda al menos una tercera entrada sobre diseño responsivo. Esta semana creo que la cosa va a andar chunga para conectarme mucho rato, así que igual tarda hasta el viernes o sábado. O igual lo puedo hacer mañana, a saber. En todo caso, un saludo.