hand Inicio

SERVICIOS

Salir

nochedía

Programar una alternativa a NProgress con Gatsby

1300 palabras
5 minutos
July 10, 2020

La librería Nprogress nos permite emular el efecto que en su día ciertos sitios punteros (Google, YouTube, Medium) desarrollaron, que no es más que un elemento gráfico que nos da una respuesta visual cuando la transición entre páginas es demasiado lenta

Prólogo

La librería nprogress nos permite añadir un efecto gráfico a la transición entre páginas que nos permite "notar" que ha habido un cambio en nuestra web

Y cómo funciona?

Vamos a verlo y a implementar una alternativa más ligera, con Gatsby

Se trata de ver y reformular la adaptación de nprogress en Gatsby, adaptación hecha con el plugin gatsby-plugin-nprogress

El resultado será éste

image

Vamos allá

  1. Prólogo
  2. Implementación en Gatsby
  3. Reformulando nprogress
  4. Css
  5. Construyendo un hook
  6. Añadiendo el elemento
  7. Modificando el DOM
  8. Afinando la estética

Implementación en Gatsby

Mirando el gatsby-plugin-nprogress, el código es sencillo (sinónimo de bueno), elegante y corto, y si lo exploramos aquí vemos que se trata de

  • Utilizar nprogress tal y como nos dicen en su página de instrucciones

  • E implementarlo utilizando la API de Gatsby de un modo muy concreto

Esto se ubica en el archivo gatsby-browser.js

En gatsby tenemos dos archivos en esa categoría, el gatsby-browser.js y el gatsby-ssr.js

La diferencia entre los dos?

Uno se ejecuta en el cliente, otro en el servidor

Como en Gatsby no tenemos servidor, esto implica que se ejecuta cuando compilamos el código con gatsby build

Pues leer acerca de esto aquí y aquí, y en el artículo de Chris Biscardi

En este caso queremos ejecutar código cuando las rutas cambian, y esto lo podemos hacer en gatsby-browser.js

En concreto utilizamos onRouteUpdateDelayed y onRouteUpdate (podemos consultar la API aquí)

  • El primero se ejecuta cuando el loading de la página se demora por más de 1 segundo
  • El segundo se ejecuta siempre

Y con esto ya tenemos la manera de ejecutar nuestro código cada vez que una página se demore

Reformulando nprogress

Si ahora nos vamos al archivo central de nprogress vemos que lo que hace grosso modo es cargar un archivo css en el documento

Esto es fantástico ya que nos permite simplemente modificar el archivo css y ya lo tenemos (casi sin necesidad de tocar nada más)

Pero por otro lado podemos ver el trabajo necesario para poder entender e inyectar ese css, un trabajo que se delega a la librería css, y eso no deja de ser algo que tendrá cierta factura computacional (aunque posiblemente insignificante)

El equilibrio eterno entre eficiencia y facilidad de uso

Aquí me planteo hacer una estructura de mínimos, más sencilla, y que me dé un resultado parecido y satisfactorio

Css

Lo primero es representar el mismo efecto, que no es más que una fila con height mínimo que va aumentando de width con el tiempo, simulando una especie de relación con la carga de la página

Eso nprogress lo hace con una suerte de función random (la tienes a continuación)

Y lo implementa ejecutando periódicamente esto

En definitiva necesitamos añadir un elemento div con un css similar a este, y hacerlo sólo cuando tengamos un evento onRouteUpdateDelayed

La manera de hacerlo? Podríamos simplemente añadirlo en nuestro website gatsby

O podríamos hacerlo vía plugin y así hacerlo más reutilizable (tienes la documentación de los plugins aquí)

Me quedo con lo primero, y lo intentaré hacer con un simple hook

Construyendo un hook

Qué quiero que haga el hook?

Quiero utilizarlo en mi gatsby-browser, y que se encargue de todo

Empiezo creando un archivo al que llamo useProgress.js

Y tiene que hacer las siguientes cosas:

  • Devolver un objeto con las funciones start() y done()
  • Añadir un elemento div que le pasaré como argumento

Y para usarlo, algo así

Con lo cual el esqueleto del hook sería algo así

Lo primero entonces es plantearse cómo puedo añadir el element que recibo en el virtual DOM(?)

Con gatsby puedo hacerlo con el ancla onRenderBody, y con el argumento setPostBodyComponents, puedes leerlo aquí

Esta ancla sólo existe en el servidor, esto es en gatsby-ssr.js

Por lo tanto, esto nos invita a utilizar el ' hook' en ese mismo archivo

Algo así

Y la manera de cambiar ese elemento será con un useRef

En nprogress utilizan la propiedad css transform y translate3d (documentación), que nos permite modificar la posición de un elemento en un espacio tridimensional

Pero para qué queremos 3D cuando aquí simplemente se trata de una barra que se va llenando de color?

En su lugar, vamos a cambiar simplemente el width de la barra, y listos

Eso sí, voy a añadirle un componente css base al hook para que podamos tener algo por defecto y simplificar el uso del mismo

Añadiendo el elemento

Vamos a probar a ver si podemos añadir el elemento con éxito

Y vemos que sí, que así estamos añadiendo el componente <Bar /> al documento

Ahora lo que queremos es modificar este componente cada vez que cambiemos de página, algo que hacemos como se hace con el plugin de gatsby que he comentado antes

Pero esto no funcionará

El problema? que el objeto progress no está (evidentemente) accesible

Cómo podemos hacerlo?

Lo suyo es, en lugar de utilizar onRenderBody, utilizar wrapRootElement, una ancla que está en gatsby-ssr y también en gatsby-browser

Este hook, el wrapRootElement nos permite añadir componentes que no se re-escribirán cuando el usuario cambie de ruta

Esto es perfecto por ejemplo para añadir lógicas de autentificación, o dicho de otro modo, viene a ser una suerte de useEffect en todo nuestro website

Todo lo que esté en wrapRootElement se ejecutará sólo una vez y quedará persistente en la aplicación, a no ser que se haga un refresco con el navegador

Pues vamos allá

Y para que esté disponible he movido progress fuera del componente

Y ahora sólo nos falta añadir la lógica de la estética

Pero esto no funciona

Por qué no?

Perque cuando hago const progress = useProgress()

Pienso que estoy utilizando un custom hook (lógico)

Pero si quiero utilizar un useState, entonces veo que nos salta el error de que estamos violando las normas de los hooks

Es decir, si queremos utilizar un hook, tenemos que hacerlo dentro de un componente React, sea un hook oficial o un custom hook

Esto es algo que no podemos hacer en gatsby-browser.js, por lo que necesitamos repensarlo todo

  • Estamos añadiendo una barra siempre con wrapRootElement (podríamos hacerlo sólo cuando tengamos un evento onRouteUpdateDelayed
  • Estamos queriendo eliminar la barra cuando tengamos el evento onRouteUpdate
  • Pero al no poder definir estados ni hooks en gatsby-browser.js, cómo podemos comunicarnos entre los dos eventos?

La solución es volver a los clásicos, al DOM original, algo que por otro lado también es lo que hace nprogress

Modificando el DOM

  • Primero, añadir el elemento con wrapRootElement
  • Segundo, modificarlo con onRouteUpdateDelayed
  • Tercero, terminarlo de modificar con onRouteUpdate

Vamos allá, y le cambio el nombre de useProgress ya que ya no es un hook, aunque en espíritu lo sigue siendo

Fíjate que he incluido una rutina en onRouteUpdate para poder simular cierto delay de 4 segundos y así poder visualizar mejor lo que hacemos

Bien, pues con esto ya lo tenemos, modificamos el width de la barra una vez accedemos a ella con el clásico getElementById, le modificamos el estilo, y listos

Ahora nos queda pulirlo

Afinando la estética

Necesitamos dos cosas

  • Que haya un incremento del width
  • Que la barra desaparezca después de cargarse la página

Lo conseguimos así

Aquí utilizamos setInterval como funciona clásicamente

Si esto fuera React (si lo estuviéramos ahora estaría utilizando el useState) el uso de setInterval tiene su miga, si te interesa aquí lo tienes muy bien explicado

Ahora lo único que nos queda es darle ese matiz random ya que de momento la progresión es totalmente simétrica

Pero yo en lugar de darle ese efecto trompicones, prefiero jugar con las inercias para tener un movimiento más dinámico

Al final, mi código queda como ves

Aquí para conseguir que el navegador se "entere" de los cambios tengo que jugar con paradas en la ejecución obligatorias

Esto es así ya que sino, lo que hacen los navegadores es agrupar instrucciones y ejecutarlas todas de golpe. Esto lo evito "esperando" con el await wait

Y para sacar las curvas cubic-bezier he utilizado herramientas como ésta o con el mismo developer tools de Chrome

Y para terminar, decido que también quiero que haya un efecto visual cuando se cambia de página (no sólo cuando haya una demora)

Y listos

🙋‍♂️

Qué tal el curso?
👌 Bien 🙌🙌
👍 Bien, pero algunas cosas podrían explicarse mejor 😬
🤷‍♂️ Da por sentadas demasiadas cosas 😒
🤷‍♂️ A ver, hay poca chicha 😬
🤷‍♂️ Los ejemplos no son muy claros 🙇‍♂️
🤷‍♂️ No se entiende, está mal escrito 👎
✍️ Hay errores, revísalo en cuanto puedas 🙏

Quizá te interese

Privacidad
by kuworking.com
[ 2020 >> kuworking ]