Transiciones de páginas suaves y bonitas con la API de History Web
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.



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.
- El primer parámetro,
state, debería ser un objeto que contenga datos arbitrarios. Estos datos serán accesibles a través dewindow.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. - Los dos últimos parámetros son
titley -
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:



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 eventopopstatede forma diferente en la carga de la página. Chrome (antes de la v34) y Safari siempre emiten un eventopopstateen 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:



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.
- Cargue jQuery e History.js dentro del
headdel documento. Puede cargarlo como una dependencia de proyecto a través de Bower, o a través de un CDN como CDNJS o JSDelivr. - Envuelva el encabezado, el contenido y el pie de página en una
divcon el ajuste de IDwrap;<div id="wrap"></div> - 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.
- Cada enlace de menú se asigna a una clase
page-linkque utilizaremos para seleccionar estos menús. - Por último, le asignamos a cada enlace un atributo
titleque pasaremos apushState()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:
- En
la primera línea, seleccionamos el elemento,
<div id="wrap"></div>, que envuelve todo el contenido de nuestro sitio web. - Adjuntamos el evento de clic. Pero,
como se puede ver arriba, lo adjuntamos al elemento
#wrapen 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#wrapes responsable de escuchar los eventos de clic en nombre de.page-link. - También hemos añadido
event.preventDefault()para que los usuarios no se dirijan a la página en cuestión. - 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.
- La variable
pageTitlecontiene 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. - Por último, pasamos el
pageTitley la URL de la página al método history.jspushState().
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:



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.
- 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. - Obtenemos los cambios de estado, que contienen varios datos como URL, título e id.
- A través del método jQuery
.get()recuperamos el contenido de la URL dada. - Por
último, clasificamos el elemento con un
idllamadowrapdel 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.



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.
- Introducción de un principiante a la animación CSS
- Consejo rápido: Delegación de eventos JavaScript en 4 minutos
- AJAX para diseñadores Front-End
- Tareas Async con jQuery Promises
- Desmitificando Google Analytics
Por último, no olvide visitar el sitio de demostración de este tutorial, así como el código fuente en el repositorio.



