Mejora del flujo de usuarios a través de las transiciones de página

Publicado: 2022-03-10
Resumen rápido ↬ Cada vez que se interrumpe la experiencia de un usuario, aumenta la posibilidad de que se vaya. Cambiar de una página a otra a menudo causará esta interrupción al mostrar un destello blanco sin contenido, al tardar demasiado en cargar o al sacar al usuario del contexto en el que se encontraba antes de que se abriera la nueva página.

Las transiciones entre páginas pueden mejorar la experiencia al retener (o incluso mejorar) el contexto del usuario, mantener su atención y brindar continuidad visual y comentarios positivos. Al mismo tiempo, las transiciones de página también pueden ser estéticamente agradables y divertidas y pueden reforzar la marca cuando se hacen bien.

Page Transitions

En este artículo, crearemos, paso a paso, una transición entre páginas. También hablaremos sobre los pros y los contras de esta técnica y cómo llevarla al límite.

Ejemplos

Muchas aplicaciones móviles hacen un buen uso de las transiciones entre vistas. En el siguiente ejemplo, que sigue las pautas de diseño de materiales de Google, vemos cómo la animación transmite relaciones jerárquicas y espaciales entre páginas.

¿Por qué no usamos el mismo enfoque con nuestros sitios web? ¿Por qué estamos de acuerdo con que el usuario sienta que está siendo teletransportado cada vez que cambia la página?

¡Más después del salto! Continúe leyendo a continuación ↓

Cómo hacer la transición entre páginas web

Marcos SPA

Antes de ensuciarnos las manos, debo decir algo sobre los marcos de aplicación de una sola página (SPA). Si está utilizando un marco SPA (como AngularJS, Backbone.js o Ember), la creación de transiciones entre páginas será mucho más fácil porque JavaScript ya maneja todo el enrutamiento. Consulte la documentación relevante para ver cómo hacer la transición de páginas utilizando el marco de su elección, porque probablemente haya algunos buenos ejemplos y tutoriales.

La forma incorrecta

Mi primer intento de crear una transición entre páginas se veía más o menos así:

 document.addEventListener('DOMContentLoaded', function() { // Animate in }); document.addEventListener('beforeunload', function() { // Animate out });

El concepto es simple: use una animación cuando el usuario abandona la página y otra animación cuando se carga la nueva página.

Sin embargo, pronto descubrí que esta solución tenía algunas limitaciones:

  • No sabemos cuánto tardará en cargarse la página siguiente, por lo que es posible que la animación no parezca fluida.
  • No podemos crear transiciones que combinen contenido de las páginas anterior y siguiente.

De hecho, la única forma de lograr una transición fluida y sin problemas es tener un control total sobre el proceso de cambio de página y, por lo tanto, no cambiar la página en absoluto . Por lo tanto, tenemos que cambiar nuestro enfoque del problema.

La direccion correcta

Veamos los pasos necesarios para crear una transición de fundido cruzado simple entre páginas de la manera correcta. Se trata de algo llamado pushState AJAX (o PJAX), que esencialmente convertirá nuestro sitio web en una especie de sitio web de una sola página.

Esta técnica no solo consigue transiciones suaves y agradables, sino que nos beneficiaremos de otras ventajas, que trataremos en detalle más adelante en este artículo.

Evitar el comportamiento de enlace predeterminado

El primer paso es crear un detector de eventos de click para que lo usen todos los enlaces, evitando que el navegador realice su comportamiento predeterminado y personalizando la forma en que maneja los cambios de página.

 // Note, we are purposely binding our listener on the document object // so that we can intercept any anchors added in future. document.addEventListener('click', function(e) { var el = e.target; // Go up in the nodelist until we find a node with .href (HTMLAnchorElement) while (el && !el.href) { el = el.parentNode; } if (el) { e.preventDefault(); return; } });

Este método de agregar un detector de eventos a un elemento principal, en lugar de agregarlo a cada nodo específico, se denomina delegación de eventos y es posible debido a la naturaleza de creación de eventos de la API HTML DOM.

Obtener la página

Ahora que hemos interrumpido el navegador cuando intenta cambiar la página, podemos obtener manualmente esa página usando la API Fetch. Veamos la siguiente función, que obtiene el contenido HTML de una página cuando se le proporciona su URL.

 function loadPage(url) { return fetch(url, { method: 'GET' }).then(function(response) { return response.text(); }); }

Para los navegadores que no son compatibles con Fetch API, considere agregar el polyfill o usar el antiguo XMLHttpRequest .

Cambiar la URL actual

HTML5 tiene una API fantástica llamada pushState , que permite que los sitios web accedan y modifiquen el historial del navegador sin cargar ninguna página. A continuación, lo estamos usando para modificar la URL actual para que sea la URL de la página siguiente. Tenga en cuenta que esta es una modificación de nuestro controlador de evento de clic de anclaje previamente declarado.

 if (el) { e.preventDefault(); history.pushState(null, null, el.href); changePage(); return; }

Como habrá notado, también hemos agregado una llamada a una función llamada changePage , que veremos en detalle en breve. También se llamará a la misma función en el evento popstate , que se activa cuando cambia la entrada del historial activo del navegador (como cuando un usuario hace clic en el botón Atrás de su navegador):

 window.addEventListener('popstate', changePage);

Con todo esto, básicamente estamos construyendo un sistema de enrutamiento muy primitivo, en el que tenemos modos activos y pasivos.

Nuestro modo activo está en uso cuando un usuario hace clic en un enlace y cambiamos la URL usando pushState , mientras que el modo pasivo está en uso cuando la URL cambia y el evento popstate nos notifica. En cualquier caso, vamos a llamar a changePage , que se encarga de leer la nueva URL y cargar la página correspondiente.

Analizar y agregar el nuevo contenido

Por lo general, las páginas por las que se navega tendrán elementos comunes, como header y footer de página. Supongamos que usamos la siguiente estructura DOM en todas nuestras páginas (que en realidad es la estructura de Smashing Magazine):

¡Animar!

Cuando el usuario hace clic en un enlace, la función changePage obtiene el HTML de esa página, luego extrae el contenedor cc y lo agrega al elemento main . En este punto, tenemos dos contenedores de cc en nuestra página, el primero perteneciente a la página anterior y el segundo a la página siguiente.

La siguiente función, animate , se encarga de fusionar los dos contenedores superponiéndolos, desvaneciendo el anterior, desvaneciendo el nuevo y eliminando el contenedor anterior. En este ejemplo, estoy usando la API de animaciones web para crear la animación de fundido, pero, por supuesto, puede usar cualquier técnica o biblioteca que desee.

 function animate(oldContent, newContent) { oldContent.style.position = 'absolute'; var fadeOut = oldContent.animate({ opacity: [1, 0] }, 1000); var fadeIn = newContent.animate({ opacity: [0, 1] }, 1000); fadeIn.onfinish = function() { oldContent.parentNode.removeChild(oldContent); }; }

El código final está disponible en GitHub.

¡Y esos son los conceptos básicos de la transición de páginas web!

Advertencias y limitaciones

El pequeño ejemplo que acabamos de crear está lejos de ser perfecto. De hecho, todavía no hemos tenido en cuenta algunas cosas:

  • Asegúrese de afectar los enlaces correctos.
    Antes de cambiar el comportamiento de un enlace, debemos agregar una verificación para asegurarnos de que se debe cambiar. Por ejemplo, debemos ignorar todos los enlaces con target="_blank" (que abre la página en una nueva pestaña), todos los enlaces a dominios externos y algunos otros casos especiales, como Control/Command + click (que también abre la página en una nueva pestaña).
  • Actualizar elementos fuera del contenedor de contenido principal.
    Actualmente, cuando la página cambia, todos los elementos fuera del contenedor cc siguen siendo los mismos. Sin embargo, sería necesario cambiar algunos de estos elementos (lo que ahora solo se puede hacer manualmente), incluido el title del documento, el elemento del menú con la clase active y potencialmente muchos otros según el sitio web.
  • Administrar el ciclo de vida de JavaScript.
    Nuestra página ahora se comporta como un SPA, en el que el navegador no cambia de página por sí mismo. Por lo tanto, debemos ocuparnos manualmente del ciclo de vida de JavaScript; por ejemplo, vincular y desvincular ciertos eventos, reevaluar complementos e incluir polyfills y código de terceros.

Compatibilidad con navegador

El único requisito para este modo de navegación que estamos implementando es la API pushState , que está disponible en todos los navegadores modernos. Esta técnica funciona plenamente como un realce progresivo . Las páginas aún se sirven y son accesibles de la manera habitual, y el sitio web seguirá funcionando normalmente cuando JavaScript esté deshabilitado.

Si está utilizando un marco SPA, considere usar la navegación PJAX en su lugar, solo para mantener la navegación rápida. Al hacerlo, obtiene soporte heredado y crea un sitio web más compatible con SEO.

Yendo aún más lejos

Podemos continuar empujando el límite de esta técnica optimizando ciertos aspectos de la misma. Los siguientes trucos acelerarán la navegación, mejorando significativamente la experiencia del usuario.

Usar un caché

Cambiando ligeramente nuestra función loadPage , podemos agregar un caché simple, que asegura que las páginas que ya han sido visitadas no se vuelvan a cargar.

 var cache = {}; function loadPage(url) { if (cache[url]) { return new Promise(function(resolve) { resolve(cache[url]); }); } return fetch(url, { method: 'GET' }).then(function(response) { cache[url] = response.text(); return cache[url]; }); }

Como habrás adivinado, podemos usar un caché más permanente con la API de caché u otro caché de almacenamiento persistente del lado del cliente (como IndexedDB).

Animación de la página actual

Nuestro efecto de fundido cruzado requiere que la siguiente página esté cargada y lista antes de que se complete la transición. Con otro efecto, podríamos querer comenzar a animar la página anterior tan pronto como el usuario haga clic en el enlace, lo que le daría al usuario una respuesta inmediata, una gran ayuda para el rendimiento percibido.

Mediante el uso de promesas, el manejo de este tipo de situaciones se vuelve muy fácil. El método .all crea una nueva promesa que se resuelve tan pronto como se resuelven todas las promesas incluidas como argumentos.

 // As soon as animateOut() and loadPage() are resolved… Promise.all[animateOut(), loadPage(url)] .then(function(values) { …

Precargar la página siguiente

Usando solo la navegación PJAX, los cambios de página suelen ser casi el doble de rápidos que la navegación predeterminada, porque el navegador no tiene que analizar ni evaluar ningún script o estilo en la nueva página.

Sin embargo, podemos ir aún más lejos si empezamos a precargar la siguiente página cuando el usuario pasa el cursor por encima o empieza a tocar el enlace.

Como puede ver, generalmente hay de 200 a 300 milisegundos de retraso en el desplazamiento y el clic del usuario. Este es un tiempo muerto y suele ser suficiente para cargar la página siguiente.

Dicho esto, realice una búsqueda anticipada inteligente porque puede convertirse fácilmente en un cuello de botella. Por ejemplo, si tiene una lista larga de enlaces y el usuario se desplaza por ella, esta técnica obtendrá todas las páginas porque los enlaces pasan debajo del mouse.

Otro factor que podríamos detectar y tener en cuenta para decidir si precargar es la velocidad de conexión del usuario. (Tal vez esto sea posible en el futuro con la API de información de red).

Salida parcial

En nuestra función loadPage , estamos recuperando el documento HTML completo, pero en realidad solo necesitamos el contenedor cc . Si usamos un lenguaje del lado del servidor, podemos detectar si la solicitud proviene de una llamada AJAX personalizada en particular y, de ser así, generar solo el contenedor que necesita. Mediante el uso de la API de encabezados, podemos enviar un encabezado HTTP personalizado en nuestra solicitud de recuperación.

 function loadPage(url) { var myHeaders = new Headers(); myHeaders.append('x-pjax', 'yes'); return fetch(url, { method: 'GET', headers: myHeaders, }).then(function(response) { return response.text(); }); }

Luego, en el lado del servidor (usando PHP en este caso), podemos detectar si nuestro encabezado personalizado existe antes de generar solo el contenedor requerido:

 if (isset($_SERVER['HTTP_X_PJAX'])) { // Output just the container }

Esto reducirá el tamaño del mensaje HTTP y también reducirá la carga del lado del servidor.

Terminando

Después de implementar esta técnica en un par de proyectos, me di cuenta de que una biblioteca reutilizable sería de gran ayuda. Me ahorraría tiempo al implementarlo en cada ocasión, liberándome para concentrarme en los efectos de transición en sí.

Así nació Barba.js, una pequeña biblioteca (4 KB minimizada y gZip'd) que abstrae toda esta complejidad y proporciona una API agradable, limpia y simple para que la usen los desarrolladores. También tiene en cuenta las vistas y viene con transiciones reutilizables, almacenamiento en caché, captación previa y eventos. Es de código abierto y está disponible en GitHub.

Conclusión

Hemos visto ahora cómo crear un efecto de fundido cruzado y los pros y los contras de usar la navegación PJAX para transformar efectivamente nuestro sitio web en un SPA. Además del beneficio de la transición en sí, también hemos visto cómo implementar mecanismos simples de almacenamiento en caché y búsqueda previa para acelerar la carga de nuevas páginas.

Todo este artículo se basa en mi experiencia personal y en lo que aprendí al implementar transiciones de página en proyectos en los que he trabajado. Si tiene alguna pregunta, no dude en dejar un comentario o comunicarse conmigo en Twitter: ¡mi información está a continuación!

Lectura adicional en SmashingMag:

  • Transiciones inteligentes en el diseño de la experiencia del usuario
  • Diseñar en la transición a un mundo multidispositivo
  • Proporcionar una experiencia nativa con tecnologías web