1. Web Design
  2. UX/UI
  3. UX Design

Transiciones de páginas suaves y bonitas con la API de History Web

En este tutorial vamos a crear un sitio web con transiciónes de páginas perfectamente suaves sin la habitual actualización agresiva de la página. Navegue a través de las páginas de la demo para que vea lo que quiero decir.
Scroll to top

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

En este tutorial vamos a crear un sitio web con transiciónes de páginas perfectamente suaves sin la habitual actualización agresiva de la página. Navegue a través de las páginas de la demo para que vea lo que quiero decir.

Para lograr este efecto, utilizaremos la API de History Web. En pocas palabras, esta API se utiliza para alterar el historial del navegador. Nos permite cargar una nueva URL, cambiar el título de la página y, a la vez, registrarla como una nueva visita en el navegador sin tener que cargar la página.

Esto suena confuso, pero abre una serie de posibilidades–como ser capaz de servir transiciones de página más suave y dar una sensación de rapidez que mejora la experiencia del usuario. Probablemente ya ha presenciado el API de History Web en acción en una serie de sitios web y aplicaciones web, como Trello, Quartz y Privacidad.

QuartzQuartzQuartz
El bastante abstracto (y bastante agradable) sitio web de Quartz

Antes de seguir adelante, veamos primero una API en particular que vamos a implementar en el sitio web.

La API de History Web, en Breve

Para acceder a la API de historial web, primero escribimos window.history y seguimos esto con una de las API; Un método o una propiedad. En este tutorial nos centraremos en el método pushState(), por lo que:

1
window.history.pushState( state, title, url );

Como se puede ver en el fragmento anterior, el método pushState() toma tres parámetros.

  1. El primer parámetro, state, debería ser un objeto que contenga datos arbitrarios. Estos datos serán accesibles a través de window.history.state. En una aplicación del mundo real, pasaríamos datos como un ID de página, una URL o entradas serializadas derivadas de un formulario.
  2. Los dos últimos parámetros son title y
  3. url. Estos dos cambios de la URL y el título del documento en el navegador, así como registrarlos como una nueva entrada en el historial del navegador.

Vamos a diseccionar el siguiente ejemplo para entender mejor cómo funciona el método pushState().

1
(function( $ ){
2
3
  $( "a" ).on( "click", function( event ) {
4
5
		event.preventDefault();
6
7
		window.history.pushState( { ID: 9 }, "About - Acme", "about/" );
8
	} );
9
10
})( jQuery );

En el código anterior, un vínculo asociado con el evento click despliega el método pushState(). Al hacer clic en el enlace, esperamos que el código cambie el título del documento y la URL:

Browsers WindowBrowsers WindowBrowsers Window
De arriba a abajo: Chrome, Firefox, Opera.

Y lo hace; La captura de pantalla muestra que la URL se cambia a "about/" como se define en el método pushState(). Y como el método pushState() crea un nuevo registro en el historial del navegador, podemos volver a la página anterior a través del botón Atrás del navegador.

Sin embargo, todos los navegadores en este ejemplo ignoran actualmente el parámetro de title. Puede ver en la captura de pantalla que el documento no cambia a About - Acme como se especifica. Además, llamar al método pushState() no activará también el evento popstate; Un evento que se envía cada vez que cambia la historia-!algo que necesitamos! Existen algunas discrepancias sobre cómo manejan los navegadores este evento, como se indica en MDN:

"Los navegadores tienden a manejar el evento popstate de forma diferente en la carga de la página. Chrome (antes de la v34) y Safari siempre emiten un evento popstate en la carga de la página, pero Firefox no. "

Necesitaremos una biblioteca como alternativa para hacer que las API de History Web funcionen de manera consistente en el navegador sin ningún obstáculo.

Conoce a History.js

Puesto que el método pushState() no funciona a su máximo potencial, en este tutorial vamos a aprovechar History.js. Como su nombre indica, esta biblioteca de JavaScript es un polyfill, replicando las API de historial nativas que funcionan en diferentes navegadores. También expone un conjunto de métodos similares a los APIs nativos, aunque con pocas diferencias.

Como se mencionó anteriormente, la API nativa del navegador se llama a través del objeto de ventana history con la minúscula "h", mientras que se accede a la API de History.js a través de History con la mayúscula "H". Teniendo en cuenta el ejemplo anterior y asumiendo que tenemos el archivo history.js cargado, podemos revisar el código, como sigue (de nuevo, observe la mayúscula "H").

1
window.History.pushState( {}, title, url );

Espero que esta breve explicación sea fácil de entender. De lo contrario, aquí hay algunas referencias adicionales si desea obtener más información acerca de la API de History Web.

Crear nuestro sitio web estático

En esta sección no discutiremos cada paso necesario para construir un sitio web estático en detalle. Nuestro sitio web es sencillo, como se muestra en la siguiente captura de pantalla:

Página de inicio del sitio web

Usted no tiene que crear un sitio web que se ve exactamente igual; Usted es libre de agregar cualquier contenido y de crear tantas páginas como usted necesita. Sin embargo, hay algunos puntos particulares que debe tener en cuenta con respecto a la estructura HTML y el uso de id y atributos  class para algunos elementos.

  1. Cargue jQuery e History.js dentro del head del documento. Puede cargarlo como una dependencia de proyecto a través de Bower, o a través de un CDN como CDNJS o JSDelivr.
  2. Envuelva el encabezado, el contenido y el pie de página en una div con el ajuste de ID wrap; <div id="wrap"></div>
  3. Hay algunos elementos de navegación en el encabezado del sitio web y el pie de página. Cada menú debe estar apuntando a una página. Asegúrese de que las páginas existen y tienen contenido.
  4. Cada enlace de menú se asigna a una clase page-link que utilizaremos para seleccionar estos menús.
  5. Por último, le asignamos a cada enlace un atributo title que pasaremos a pushState() para determinar el título del documento.

Tomando todo esto en cuenta, nuestro marcado HTML se verá como lo que sigue:

1
<head>
2
	<script src="jquery.js"></script>
3
	<script src="history.js"></script>
4
</head>
5
<body>
6
	<div id="wrap">
7
		<header>
8
			<nav>
9
				<ul>
10
					<li><a class="page-link" href="./" title="Acme">Home</a></li>
11
					<li><a class="page-link" href="./about.html" title="About Us">About</a></li>
12
					<!-- more menu -->
13
				</ul>
14
			</nav>
15
		</header>
16
		<div>
17
			<!-- content is here -->
18
		</div>
19
		<footer>
20
			<nav>
21
				<ul>
22
					<li><a href="tos.html" class="page-link" title="Terms of Service">Terms</a></li>
23
					<!-- more menu -->
24
				</ul>
25
			</nav>
26
			<!-- this is the footer -->
27
		</footer>
28
	</div>
29
</body>

Cuando haya terminado de construir su sitio web estático, podemos pasar a la sección principal de este tutorial.

Aplicación de la API de History We

Antes de comenzar a escribir cualquier código, necesitamos crear un nuevo archivo para mantener nuestro JavaScript; Lo nombraremos script.js y cargamos el archivo en el documento antes de la etiqueta de cierre body.

Vamos a agregar nuestro primer pedazo de código para cambiar el título del documento y la URL al hacer clic en el menú de navegación:

1
// 1.

2
var $wrap = $( "#wrap" );
3
4
// 2.

5
$wrap.on( "click", ".page-link", function( event ) {
6
	
7
	// 3.

8
	event.preventDefault();
9
	
10
	// 4.

11
	if ( window.location === this.href ) {
12
		return;
13
	}
14
	
15
	// 5.

16
	var pageTitle = ( this.title ) ? this.title : this.textContent;
17
		pageTitle = ( this.getAttribute( "rel" ) === "home" ) ? pageTitle : pageTitle + " — Acme";
18
	
19
	// 6.

20
	History.pushState( null, pageTitle, this.href );
21
} );

He dividido el código en varias secciones numeradas. Esto facilitará la localización del código con la siguiente referencia:

  1. En la primera línea, seleccionamos el elemento, <div id="wrap"></div>, que envuelve todo el contenido de nuestro sitio web.
  2. Adjuntamos el evento de clic. Pero, como se puede ver arriba, lo adjuntamos al elemento #wrap en lugar de adjuntar el evento directamente en cada menú de navegación. Esta práctica se conoce como delegación de eventos. En otras palabras, nuestro elemento #wrap es responsable de escuchar los eventos de clic en nombre de .page-link.
  3. También hemos añadido event.preventDefault() para que los usuarios no se dirijan a la página en cuestión.
  4. Si la URL del menú que se hace clic es la misma que la ventana actual, no es necesario proceder a la siguiente operación, simplemente porque no es necesario.
  5. La variable pageTitle contiene el formato de título, derivado del atributo de título de enlace o el texto del enlace. Cada título de página sigue la convencion {Título de página} - Acme, excepto para la página de inicio. "Acme" es nuestro nombre ficticio de la compañía.
  6. Por último, pasamos el pageTitle y la URL de la página al método history.js pushState().

En este punto, cuando hacemos clic en el menú de navegación, el título, así como la URL debe cambiar en consecuencia como se muestra a continuación:

The browser window with updated URL and titleThe browser window with updated URL and titleThe browser window with updated URL and title
Se cambian el título de la página y la URL

Sin embargo, ¡el contenido de la página sigue siendo el mismo! No se actualiza para coincidir con el nuevo título y la nueva URL.

Contenido

Necesitamos agregar las siguientes líneas de código para reemplazar el contenido real de la página.

1
// 1.

2
History.Adapter.bind( window, "statechange", function() {
3
	
4
	// 2.

5
	var state = History.getState();
6
	
7
	// 3.

8
	$.get( state.url, function( res ) {
9
10
		// 4.

11
		$.each( $( res ), function( index, elem ) {
12
			if ( $wrap.selector !== "#" + elem.id ) {
13
				return;
14
			}
15
			$wrap.html( $( elem ).html() );
16
		} );
17
18
	} );
19
} );

Una vez más, el código aquí se divide en varias secciones numeradas.

  1. La primera línea del código escucha el cambio de Historial realizado a través del método history.js pushState() y ejecuta la función adjunta.
  2. Obtenemos los cambios de estado, que contienen varios datos como URL, título e id.
  3. A través del método jQuery .get() recuperamos el contenido de la URL dada.
  4. Por último, clasificamos el elemento con un id llamado wrap del contenido recuperado y, finalmente, reemplazamos el contenido de la página actual por él.

Una vez que se agrega, el contenido ahora debe ser actualizado cuando hacemos clic en el menú de navegación. Como se mencionó, también podemos acceder a las páginas visitadas de un lado a otro a través de los botones Atras y Adelante del navegador.

The browser window now with the updated contentThe browser window now with the updated contentThe browser window now with the updated content

Nuestro sitio web es presentable en este punto. Sin embargo, nos gustaría dar un paso más con la adición una pequeña animación para darle actividad y, al finalizar, el sitio web se ve más convincente.

Agregar animaciones y transiciones

Animación en esta situación sólo tiene que ser simple, por lo que vamos a escribir todo desde cero, en lugar de cargar animaciones a través de una biblioteca como Animate.css, Motion UI de ZURB, o Effeckt.css. Nombraremos la animación slideInUp, de la siguiente manera:

1
@keyframes slideInUp {
2
	from {
3
		transform: translate3d(0, 10px, 0);
4
		opacity: 0;
5
	}
6
	to {
7
		transform: translate3d(0, 0, 0);
8
		opacity: 1;
9
	}
10
}

Como su nombre indica, la animación se deslizará el contenido de la página de abajo hacia arriba junto con la opacidad del elemento. Aplique la animación al elemento que envuelve el contenido principal de la página, como se indica a continuación.

1
.section {
2
	animation-duration: .38s;
3
	animation-fill-mode: both;
4
	animation-name: slideInUp;
5
}

La transición de una página a otra debe sentirse más suave una vez que se aplica la animación. ¡Aquí, puedes parar ya! Nuestro sitio web está hecho y estamos listos para desplegarlo para que el mundo lo vea.

Sin embargo, hay una cosa más que puede que tenga que considerar agregar, especialmente para aquellos que quieren controlar el número de visitas y el comportamiento de los visitantes en su sitio web.

Tenemos que agregar Google Analytics para realizar el seguimiento de cada vista de página.

Google Analytics

Dado que nuestras páginas se cargarán de forma asíncrona (excepto la página inicial cargada), el seguimiento del número de vista de página también se debe realizar de forma asíncrona.

Para empezar, asegúrese de que tiene el estándar de Google Analytics añadido dentro de head del documento. El código normalmente se ve así:

1
<script>
2
		(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
3
			(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
4
			m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
5
		})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
6
7
		ga('create', 'UA-XXXXXX-XX', 'auto');
8
		ga('send', 'pageview');
9
10
	</script>

Luego debemos ajustar nuestro código JavaScript para incluir el código de seguimiento de Google Analytics de modo que cada página cargada asincrónicamente también se mida como una vista de página.

Tenemos varias opciones. En primer lugar, podemos empezar a contar cuando el usuario hace clic en un vínculo de navegación, o al cambiar el título de la página y la URL, o cuando el contenido de la página se ha cargado completamente.

Vamos a optar por este último, que es posiblemente el más auténtico, y al hacerlo aprovechamos el método jQuery promise() después de cambiar el contenido de la página, de la siguiente manera:

1
$wrap.html( $( elem ).html() )
2
		.promise()
3
			.done( function( res ) {
4
5
			// Make sure the new content is added, and the 'ga()' method is available.

6
			if ( typeof ga === "function" && res.length !== 0 ) {
7
				ga('set', {
8
					page: window.location.pathname,
9
					title: state.title
10
				});
11
				ga('send', 'pageview');
12
		}
13
	});

Eso es todo, ahora tenemos la vista de página grabada en Google Analytics.

Concluyendo

En este tutorial hemos mejorado un simple sitio web estático con el API de History Web para hacer la transición de página más suave, la carga más rápida y, en general, ofrecer una mejor experiencia a nuestros usuarios. Al final de este tutorial, también implementamos Google Analytics para registrar la vista de página de usuario de forma asíncrona. Además, nuestro sitio web es perfectamente rastreable por los robots de los motores de búsqueda ya que es, como se ha mencionado, un simple sitio web HTML.

Este fue un tutorial explicativo, explicando muchas cosas como Animacion CSS, jQuery Ajax, y jQuery Promise. Aquí hay un puñado de referencias para mirar, y reforzar lo que ha aprendido.

Por último, no olvide visitar el sitio de demostración de este tutorial, así como el código fuente en el repositorio.