Guía a tus usuarios con JavaScript y ShepherdJs

Programa un tour que haga de guía a tus usuarios con JavaScript y ShepherdJs. Así no se perderán ninguna funcionalidad clave de tu web.

https://shepherdjs.dev/ Guide your users through a tour of your app.

Los artículos de libreriasjs suelen ir acompañados de un ejercicio práctico. Haz click en la siguiente imágen para ver el resultado final de la práctica de hoy:

Implementa ShepherdJs
Haz click en la imagen para ver el ejercicio acabado, en una ventana nueva

Si quieres ir directamente a la parte práctica, haz click aquí

Tours interactivos para guiar al usuario por la interfaz.

Una de las sensaciones más frustrantes que puedes experimentar como desarrollador, es descubrir que funcionalidades de tu software, pasan desapercibidas para todo el mundo.

Podrías pensar que eso es un problema de diseño de experiencia de usuario, e interfaz gráfica, y que poco tiene que ver con la programación. Y, probablemente no te faltará razón.

Sin embargo, se dan situaciones donde el cliente de tu app, ignora completamente la existencia de ciertas opciones que pueden ser de su interés. A pesar de que la UX y la UI están bien resueltas. 

Ya sea debido a que ciertas funciones se han agregado posteriormente. O porque conforme el proyecto ha ido escalando, su acceso ha quedado relegado a un segundo plano.

El caso es que, como desarrolladores, debemos aportar una solución a esa problemática.

Una posible respuesta ante esta situación, es crear un tour interactivo que ayude a los usuarios a recorrer la interfaz. Una suerte de guía, que destaque puntos de interés, y dé a conocer acciones al usuario, que de otro modo, le habrían pasado por alto.

Flautista de Hamelín guiando usuarios
Flautista de Hamelín guiando usuarios

Estoy seguro de que en tu mente ya has identificado algún ejemplo de este sistema. Sin ir más lejos, Google utiliza este recurso para guiar la interacción a través de “webapps” tan conocidas como Google Analytics y Search console.

Se trata de una solución especialmente útil para personas acceden por primera vez al software. De un rápido vistazo, y de forma poco intrusiva, se pueden dar a conocer las opciones y capacidades de la aplicación.

Ejemplo de guía virtual
Ejemplo de guía virtual

Es posible que pienses “Suena genial, pero tarde o temprano, llega el momento donde habrá que implementarlo técnicamente”. Bien, pues precisamente aquí es donde entra en juego ShepherdJs.

ShepherdJs, la librería JavaScript que te hace de guía.

ShepherdJs es una librería JavaScript desarrollada por el equipo de Ship Shape, para guiar a los usuarios a través de cualquier página web.

El repositorio en Github de esta herramienta se encuentra activamente mantenido por más de 90 personas. Está valorado con más de 10.5K estrellas, y da cobertura a una media de 92K descargas semanales en npmjs.com.

Como ves, se trata de una librería muy popular, que además cuida aspectos clave como la accesibilidad, la personalización y la integración con otros “frameworks”.

Instalar la librería en cualquier proyecto frontend es tan simple como lanzar el siguiente comando NPM:

npm install shepherd.js

Como es habitual, ese proceso instalará el recurso en tu directorio “node_modules” como una nueva dependencia.

De este modo, podrás importar Shepherd mediante la instrucción:

import Shepherd from "shepherd.js";

El objeto Shepherd es un “namespace” que expone dos clases principales. 

Por un lado existe “Shepherd.Tour”, una clase que, como su nombre indica, permite definir el recorrido del tour a través de tu web.

En el momento de instanciar esta clase, podemos pasar un objeto para describir su comportamiento. Entre los parámetros de configuración encontramos algunos tan útiles como los siguientes:

  • defaultStepOptions: Una propiedad en forma de objeto, que permite declarar las opciones por defecto de cada “Step”. Más adelante veremos cuáles son estas.
  • exitOnEsc: Variable booleana, para decidir si el usuario puede terminar el tour mediante la tecla “ESC”.
  • tourName: Esta propiedad permite asignar un nombre al tour. Dicha acción también tiene un efecto en el layout HTML, ya que agrega un atributo “data-shepherd-active-tour” al body cuando el tour está activo.
  • steps: Quizá la propiedad más importante. Se trata de una array de JSONs, que SheperdJs utilizará para instanciar objetos “Step”. Más tarde veremos que también existe el método “addSteps()” para ese mismo propósito. 
  • useModalOverlay: Booleano para agregar una capa semi opaca que enfatice la posición del tour en cada momento.

Este es un ejemplo de cómo crear un objeto “tour” definiendo algunas opciones para el constructor.

const tour = new Shepherd.Tour({
  useModalOverlay: true,
  defaultStepOptions: {
    classes: 'shadow-md bg-purple-dark',
    scrollTo: true
  }
});

Por supuesto, la variable “tour” dispone de un conjunto de métodos. Los más destacables son:

  • addSteps(): para incluir nuevos pasos en el tour.
  • next() y back(): para avanzar o retroceder en el tour.
  • start(): para iniciar el tour programáticamente.
  • complete(): para finalizar el tour.

Tienes el listado completo de opciones y métodos en este enlace:

https://shepherdjs.dev/docs/Tour.html

Por otro lado, también está «Shepherd.Step«, que describe métodos, propiedades y eventos de cada «paso» dentro del tour.

El objeto de configuración de ésta segunda clase admite claves y valores como los presentados a continuación:

  • attachTo: Seguramente el parámetro más importante. Admite un objeto JSON con los atributos “element” y “on”. “element” es un selector CSS que determinará sobre qué elemento del DOM debe detenerse ese punto del tour. “on” es la posición del tooltip, de modo que acepta valores como “bottom”, “top”, “left”, “auto”, etc.
  • buttons: Permite agregar botones dinámicamente al “tooltip”. Lo cual es genial para definir qué acciones puede realizar el usuario en cada paso del tour.
  • title: Para agregar un texto en forma de título en un determinado paso.
  • text. Para agregar una descripción después del título.

Un ejemplo de instancia de “Step” sería el siguiente:

const new Shepherd.Step(tour, {
  attachTo: { element: '.some .selector-path', on: 'left' },
  title: "Example Step",
  "text": "Description of the step"
});

Y como era de esperar, también pone a disposición del desarrollador diversos métodos específicos de esta clase.

  • isOpen(): Devuelve una booleana indicando si el paso está activo.
  • getTour(): Función para recuperar el objeto “tour” al que pertenece el paso.
  • getElement(): Método útil si en un momento determinado deseamos obtener el elemento de la interfaz sobre el que se ha detenido el tour.
  • updateStepOptions(): Instrucción por si es preciso sobrescribir las opciones enviadas en el constructor.

De nuevo, no he listado todas las capacidades de “Step”, pero puedes consultarlas al completo en este enlace:

https://shepherdjs.dev/docs/Step.html

Con este breve repaso a su API, ya estamos en disposición de crear nuestro propio tour.

Implementar ShepherdJs con JavaScript para guiar a tus usuarios

Para no alargar más de lo necesario este texto, recuperaré el layout que construímos en el artículo de hace unas semanas. En esa publicación generamos un “dashboard” ficticio, el cual nos viene muy bien como base para incluir la funcionalidad de ShepherdJs.

Si te interesa ver cómo construímos ese layout, te dejo el enlace al anterior post.

Crear capturas de pantalla con HTML2Canvas

También te dejo el enlace al código del resultado final del ejercicio de hoy. Por si te hace falta para seguir el resto del tutorial.

https://github.com/Danivalldo/libreriasjs/tree/master/SheperdJs

Bien, comenzamos generando el código HTML base sobre el que definiremos nuestro tour. Como ya avancé, se trata de una estructura clásica de “dashboard”, recuperada de otro ejemplo.

<body>
  <div class="app">
    <div class="sidebar-container">
      <ul class="unstyled menu">
        <li>
          <a href="#"><img src="icons/home.svg" alt="" /></a>
        </li>
        <li>
          <a href="#"><img src="icons/dashboard.svg" alt="" /></a>
        </li>
        <li>
          <a href="#"><img src="icons/user.svg" alt="" /></a>
        </li>
        <li>
          <a href="#"><img src="icons/document.svg" alt="" /></a>
        </li>
      </ul>
    </div>
    <div class="main-container">
      <div class="header-container">
        <h1>Dashboard</h1>
        <div class="user-badge">
          <div class="profile-img">
            <img src="images/user.jpg" alt="" />
          </div>
          <span>Jane Doe</span>
        </div>
      </div>
      <div id="content" class="content-container">
        <div class="row">
          <div class="card bg-gradient take-screenshot-card">
            <h2>Tour virtual</h2>
            <div class="form-wrapper">
              <button class="btn with-icon tertiary tour-btn">
                <img src="icons/flag.svg" alt="" />
                <span>Realizar tour</span>
              </button>
            </div>
          </div>
        </div>
        <div class="row">
          <div class="card card-circle-projects">
            <h2>Proyectos</h2>
            <div class="text-center">
              <img
                src="images/circle.svg"
                alt=""
                class="img-chart chart-circle"
              />
            </div>
            <div class="text-center">
              <ul class="unstyled list-inline list-dots">
                <li class="text-main">Completado</li>
                <li class="dot-secondary text-secondary">En progreso</li>
              </ul>
            </div>
          </div>
          <div class="card bg-gradient">
            <h2>Meeting de producto</h2>
            <h3>02:00 - 03:00 PM</h3>
            <div class="users-meeting">
              <span>
                <img src="images/user_2.jpg" alt="" />
              </span>
              <span>
                <img src="images/user_3.jpg" alt="" />
              </span>
              <span>
                <img src="images/user.jpg" alt="" />
              </span>
            </div>
            <a href="#" class="btn tertiary"> Acceder </a>
          </div>
          <div class="card card-documents">
            <h2>Documentos</h2>
            <ul class="list-dots unstyled">
              <li>meeting-morning.pdf</li>
              <li>kickoff_december.pdf</li>
              <li>dossier-trimestre.doc</li>
            </ul>
            <a href="#" class="btn">Ver todo</a>
          </div>
        </div>
        <div class="row">
          <div id="table" class="card">
            <h2>Tabla de contenido</h2>
            <div class="table-wrapper">
              <table class="table">
                <thead>
                  <tr>
                    <th>Empresa</th>
                    <th>Contacto</th>
                    <th>País</th>
                  </tr>
                </thead>
                <tbody>
                  <tr>
                    <td>Alfreds Futterkiste</td>
                    <td>Maria Anders</td>
                    <td>Germany</td>
                  </tr>
                  <tr>
                    <td>Centro comercial Moctezuma</td>
                    <td>Francisco Chang</td>
                    <td>Mexico</td>
                  </tr>
                  <tr>
                    <td>Ernst Handel</td>
                    <td>Roland Mendel</td>
                    <td>Austria</td>
                  </tr>
                </tbody>
              </table>
            </div>
            <p>
              <a href="#" class="btn">Exportar CSV</a>
            </p>
          </div>
          <div id="chart" class="card card-linecharts">
            <h2>Evolución</h2>
            <div>
              <img src="images/linecharts.svg" alt="" class="img-chart" />
            </div>
          </div>
        </div>
      </div>
      <div class="footer-container">Fake Dashboard 2022 | libreriasJs</div>
    </div>
  </div>
</body>

Los estilos aplicados, tampoco han variado mucho respecto al anterior publicación. Siguen estando repartidos en archivos independientes, uno para cada componente de la interfaz.

Cabe destacar que en esta ocasión, además es necesario incluir los estilos CSS de SheperdJs. Por eso, es preciso añadir la siguiente línea al inicio de “index.sass”.

@import '../../node_modules/shepherd.js/dist/css/shepherd.css'

No dudes en acudir al repositorio que te he facilitado, para analizar detenidamente el resto de estilos SASS.

La parte que hay que reescribir por completo es, evidentemente, el comportamiento con JavaScript.

Iniciamos el archivo “index.js” importando estilos y la librería ShepherdJs

import "./SASS/index.sass";
import Shepherd from "shepherd.js";

Seguidamente instanciamos la clase “Shepherd.Tour” para crear un objeto del mismo modo que hemos aprendido.

const tour = new Shepherd.Tour({
  useModalOverlay: true,
  defaultStepOptions: {
    cancelIcon: {
      enabled: true,
    },
    classes: "shadow-md bg-purple-dark",
    scrollTo: { behavior: "smooth", block: "center" },
  },
});

En esta ocasión creamos el objeto “tour” definiendo “useModalOverlay” como true, y declarando unas opciones de “Step” generales. En ellas decidimos que siempre aparezca un botón de cancelar, unas clases CSS predeterminadas, y un comportamiento concreto para la animación de “scroll”.

A continuación, diseñamos el recorrido que hará la guía. Para ello nos apoyaremos en el método “addSteps()”, el cual recibe como argumento un array de objetos de configuración.

Este es el código resultante del itinerario:

tour.addSteps([
  {
    id: "step-1",
    title: "Menú de navegación",
    text: "Este es el menú principal, navega entre apartados a través de sus iconos.",
    attachTo: {
      element: ".menu",
      on: "right",
    },
    buttons: [
      {
        text: "Siguiente",
        action: tour.next,
      },
    ],
  },
  {
    id: "step-2",
    title: "Tour virtual",
    text: "Puedes repetir este tutorial en cualquier momento haciendo click en el botón de esta caja.",
    attachTo: {
      element: ".take-screenshot-card",
      on: "bottom",
    },
    buttons: [
      {
        text: "Anterior",
        action: tour.back,
      },
      {
        text: "Más información",
        action: () => {
          alert("Más información");
        },
      },
      {
        text: "Siguiente",
        action: tour.next,
      },
    ],
  },
  {
    id: "step-3",
    title: "Gráfico de proyectos",
    text: "Un primer vistazo del estado de tus proyectos",
    attachTo: {
      element: ".card-circle-projects",
      on: "auto",
    },
    buttons: [
      {
        text: "Anterior",
        action: tour.back,
      },
      {
        text: "Siguiente",
        action: tour.next,
      },
    ],
  },
  {
    id: "step-4",
    title: "Gráfico de evolución",
    text: "Evalúa como avanza el rendimiento",
    attachTo: {
      element: ".card-linecharts",
      on: "auto",
    },
    buttons: [
      {
        text: "Anterior",
        action: tour.back,
      },
      {
        text: "Siguiente",
        action: tour.next,
      },
    ],
  },
  {
    id: "step-5",
    title: "Logout de la aplicación",
    text: "Cierra la sessión a través de éste panel",
    attachTo: {
      element: ".user-badge",
      on: "auto",
    },
    buttons: [
      {
        text: "Anterior",
        action: tour.back,
      },
      {
        text: "Finalizar",
        action: tour.next,
      },
    ],
  },
]);

Antes de continuar, quiero detenerme un momento, y destacar algunos puntos. 

Todos los “steps” tienen declarado un título, una descripción, el elemento del DOM al que adherirse y botones de avanzar o retroceder. 

Pero, en el caso del segundo paso, además, incluye un tercer botón con una función customizada.

Por último, pero no por ello menos importante, cerramos el programa escuchando los eventos de “load” sobre window, y “click” sobre el botón de reset.

En los callbacks correspondientes llamaremos al método “tour.start()” para iniciar el recorrido.

window.addEventListener("load", () => {
  tour.start();
});
document.querySelector(".tour-btn").addEventListener("click", () => {
  tour.start();
});

Con esto doy por terminado este vistazo a la librería ShepherdJs, espero que haya sido tan interesante para tí leerlo, como ha sido para mí escribirlo.

Te dejo algunos recursos adicionales más, por si te apetece seguir investigando sobre esta funcionalidad.

Hasta la próxima publicación, un abrazo desarrollador!

Deja un comentario