Cómo Crear Tours Interactivos y Guías de Usuario en Laravel usando Driver.js

- Andrés Cruz - EN In english

Driver.js es un complemento ligero en JavaScript diseñado para crear guías interactivas, tutoriales y recorridos guiados (tours) en interfaces web. Su funcionamiento principal consiste en enfocar elementos específicos del DOM mediante un contenedor emergente (tooltip) descriptivo, guiando al usuario paso a paso por las secciones clave de la aplicación.

Además de la versión estándar en JavaScript, existen integraciones y adaptaciones orientadas a Laravel que permiten gestionar o renderizar la configuración del recorrido desde el lado del servidor.

  • Librería original en JavaScript: Ofrece mayor versatilidad y rendimiento, ya que la manipulación del DOM, las transiciones y el estado del recorrido se procesan completamente en el cliente.
  • Paquetes adaptados para Laravel: Permiten estructurar los pasos desde PHP o controladores. Sin embargo, dado que la interacción depende de elementos HTML en el navegador, el uso directo del plugin en JavaScript suele ser la opción más eficiente.

Opciones de Configuración y Personalización

Driver.js permite ajustar múltiples parámetros visuales y de comportamiento según los requerimientos del diseño:

  • Animaciones y opacidad: Control de la velocidad de transición y la transparencia de la capa de fondo (overlay).
  • Desplazamiento automático: Habilitación del scroll automático hacia el elemento enfocado cuando este no se encuentra visible en la pantalla.
  • Interacción del usuario: Control para permitir o bloquear el cierre al hacer clic fuera del paso actual, navegación con teclado y botones de avance o retroceso.
  • Estilos de resalte: Modalidades de iluminación para resaltar elementos individuales (highlight) o superponer paneles informativos modales.

¿Por qué utilizar un wrapper de Laravel?

La ventaja principal de emplear esta integración dedicada es la posibilidad de configurar y controlar los tours guiados directamente con sintaxis PHP desde nuestro backend. No obstante, dado que su funcionamiento radica en referenciar identificadores del DOM, aplicar estilos y manipular elementos interactivos en el navegador, la versión nativa de JavaScript suele ser más que suficiente para la mayoría de los casos.

Ambas alternativas ofrecen una experiencia idéntica para el usuario final, ya que comparten la misma base de código. La elección entre la librería original en JavaScript o el paquete integrado de Laravel dependerá principalmente del flujo de desarrollo y de la arquitectura que prefieras implementar.

Demostración práctica de las funciones

En el siguiente archivo de vista de Blade, se incluyen varios ejemplos interactivos que puedes probar. Entre ellos se destacan botones para reiniciar el recorrido, resaltados de interfaz (highlights) y modales emergentes.

En cuanto al código HTML que retornamos en la vista principal (index), este corresponde a la estructura estándar de la página sobre la cual se ejecutará el tutorial. La parte más interesante del proceso radica en cómo inicializamos cada una de las secuencias explicativas:

public function index(): View
{
    // Cada método devuelve ya el JavaScript final, así que no hay forma de
    // que un tour se "contamine" con los pasos del anterior.
    $scripts = [
        'tourCompleta' => $this->tourCompleta(),
        'highlightBanner' => $this->highlightBanner(),
        'modalBienvenida' => $this->modalBienvenida(),
        'tourConConfirmacion' => $this->tourConConfirmacionAlSalir(),
        'tourHooksPersonalizados' => $this->tourHooksPersonalizados(),
        'tourFluentSteps' => $this->tourFluentSteps(),
        'tourDesdeArray' => $this->tourDesdeArray(),
        'tourAislada' => $this->tourAislada(),
    ];

    // El último tour NO se renderiza aquí: se deja configurado en el
    // singleton para que lo emita la directiva Blade @driverjsTour.
    $this->configurarTourDeBienvenida();

    return view('driverjs.demo', $scripts + [
        // tourCompleted() consulta el almacenamiento (session por defecto)
        // para saber si el usuario ya vio el tour de bienvenida.
        'bienvenidaCompletada' => DriverJs::tourCompleted('demo-bienvenida'),
    ]);
}

Desde la plantilla principal llamamos a las funciones encargadas de retornar una instancia configurada de Driver.js con las opciones definidas previamente. Para organizar la lógica, instanciamos un objeto personalizado que se ejecuta al detectar el evento de clic en los elementos interactivos.

private function tourCompleta(): string
{
    return DriverJs::tour('demo-tour-completa')
        // ── Configuración global del driver ──────────────────────────────
        ->animate(true)                    // transiciones animadas entre pasos
        ->overlayColor('#0f172a')          // color del overlay (cualquier color CSS)
        ->overlayOpacity(0.75)             // opacidad del overlay (0.0 – 1.0)
        ->smoothScroll(true)               // scroll suave hasta el elemento resaltado
        ->allowClose(true)                 // Escape / clic en el overlay cierran el tour
        ->overlayClickBehavior('nextStep') // 'close' | 'nextStep' | expresión JS
        ->stagePadding(8)                  // separación elemento <-> cutout (px)
        ->stageRadius(12)                  // radio de las esquinas del cutout (px)
        ->allowKeyboardControl(true)       // Escape y flechas del teclado
        ->disableActiveInteraction(false)  // permite interactuar con lo resaltado

        // ── Configuración global del popover ─────────────────────────────
        ->popoverClass('demo-popover')     // clase CSS extra para el popover
        ->popoverOffset(16)                // distancia popover <-> elemento (px)
        ->showProgress()                   // muestra el "X de Y"
        ->progressText('Paso {{current}} de {{total}}')
        ->showButtons(['next', 'previous', 'close'])
        ->disableButtons([])               // botones visibles pero deshabilitados
        ->nextBtnText('Siguiente →')
        ->prevBtnText('← Anterior')
        ->doneBtnText('¡Entendido!')

        // ── Hooks globales ───────────────────────────────────────────────
        // Se pasan como TEXTO: el nombre de una función global o una
        // expresión JS (function / arrow). El paquete los emite sin comillas.
        ->onHighlightStarted('function (element, step, opts) { console.log("[demo] onHighlightStarted", element); }')
        ->onHighlighted(StepHooks::logStep())
        ->onDeselected('function (element, step, opts) { console.log("[demo] onDeselected", element); }')
        ->onPopoverRender('function (popover, opts) { popover.classList.add("is-rendered"); }')
        ->onDestroyStarted('function (element, step, opts) { console.log("[demo] onDestroyStarted"); }')
        ->onDestroyed('function (element, step, opts) { console.log("[demo] onDestroyed"); }')
        // Si sobrescribes onNextClick / onPrevClick / onCloseClick tienes que
        // mover el driver a mano (opts.driver), o el tour se queda quieto.
        ->onNextClick('function (element, step, opts) { opts.driver.moveNext(); }')
        ->onPrevClick('function (element, step, opts) { opts.driver.movePrevious(); }')
        ->onCloseClick('function (element, step, opts) { opts.driver.destroy(); }')

        // ── Steps ─────────────────────────────────────────────────────────
        // step() acepta un array de opciones, pero OJO: solo funcionan las
        // claves que son nombres de método en una sola palabra ('side',
        // 'align', 'element', 'title', 'description'). Para snake_case como
        // 'popover_class' o 'show_progress' hay que usar addStep().
        ->step('#demo-header', '👋 Bienvenido', 'Este tour pasa por casi todas las opciones del paquete.', [
            'side' => 'bottom',
            'align' => 'center',
        ])
        ->step('#demo-kpis', '📊 Métricas', 'Step con position y alineación.', [
            'side' => 'bottom',
            'align' => 'start',
        ])

        // addStep() devuelve el Step, y el Step reenvía al builder los
        // métodos que no son suyos, así que se pueden encadenar pasos.
        ->addStep('#demo-grafico')
        ->title('📈 Gráfico interactivo')
        ->description('Este step deshabilita el botón "anterior" y oculta el progreso.')
        ->top()                        // atajo de ->side('top')
        ->alignEnd()                   // atajo de ->align('end')
        ->popoverClass('demo-popover demo-popover--kpi')
        ->disableButtons(['previous']) // se ve, pero no se puede usar
        ->showProgress(false)
        ->progressText('Este es el paso {{current}} de {{total}}')
        ->onNextClick('function (element, step, opts) { console.log("[demo] siguiente desde el step", opts.state.activeIndex); opts.driver.moveNext(); }')

        ->addStep('#demo-tabla')
        ->title('🗂️ Tabla')
        ->description('Cambia el texto del botón "siguiente" solo en este paso.')
        ->left()
        ->alignStart()
        ->nextBtnText('Ver más →')
        ->onPopoverRender('function (popover, opts) { popover.classList.add("is-rendered"); console.log("[demo] popover listo"); }')

        ->addStep('#demo-form')
        ->title('📝 Formulario')
        ->description('disableActiveInteraction(true) bloquea el elemento mientras está resaltado.')
        ->right()
        ->disableActiveInteraction(true)
        ->doneBtnText('🎉 ¡Listo!')

        // toJavaScript() devuelve el código; toScriptTag() lo envuelve en
        // <script>. En ambos casos el builder se resetea al terminar.
        ->toJavaScript();
}

La vista, es tu HTML normal:

<!-- Vista Blade: demostración de eventos y controladores de Driver.js -->
<section class="panel">
    <h2>Demostraciones</h2>
    <p class="hint">
        Todo este JavaScript está generado por el paquete en el controller. Abre la consola
        del navegador para ver los registros del sistema.
    </p>

    <div class="grid">
        <button class="btn primary" data-demo="tourCompleta">
            <strong>1 · Tour completo</strong>
            <small>Opciones globales, hooks, steps y posicionado</small>
        </button>

        <button class="btn" data-demo="highlightBanner">
            <strong>2 · Highlight</strong>
            <small>Resalta un elemento sin botones de navegación</small>
        </button>

        <button class="btn" data-demo="modalBienvenida">
            <strong>3 · Modal</strong>
            <small>Popover centrado, sin selección de elemento</small>
        </button>
    </div>
</section>

Implementación en el Controlador

Desde el controlador de Laravel, la construcción de los tours se maneja utilizando una interfaz fluida. Es posible definir colores de superposición (overlay), animaciones, opacidad, desplazamiento suave (smooth scroll) y hooks de eventos personalizados:

namespace App\Http\Controllers;

use Illuminate\View\View;
use RealRashid\LaravelDriverJs\Facades\DriverJs;

class DriverJsDemoController extends Controller
{
    /**
     * Define un tour guiado completo desde el controlador.
     */
    private function tourCompleta(): string
    {
        return DriverJs::tour('demo-tour-completa')
            ->animate(true)
            ->overlayColor('#0f172a')
            ->overlayOpacity(0.75)
            ->smoothScroll(true)
            ->allowClose(true)
            ->popoverClass('demo-popover')
            ->showProgress()
            ->nextBtnText('Siguiente →')
            ->prevBtnText('← Anterior')
            ->doneBtnText('¡Entendido!')
            ->step('#demo-header', '👋 Bienvenido', 'Este tour repasa las opciones del paquete.', [
                'side' => 'bottom',
                'align' => 'center',
            ])
            ->toJavaScript();
    }
}

Estructura del Código y Definición de Pasos

La lógica de funcionamiento se basa en definir una secuencia de pasos identificando los elementos HTML mediante sus identificadores (ID) o clases CSS:

import { driver } from "driver.js";
import "driver.js/dist/driver.css";

const driverObj = driver({
    showProgress: true,
    animate: true,
    steps: [
        { 
            element: '#dashboard-stats', 
            popover: { 
                title: 'Panel de Estadísticas', 
                description: 'Aquí puedes consultar las métricas principales de tu cuenta.',
                side: "left",
                align: 'start'
            } 
        },
        { 
            element: '#create-report-btn', 
            popover: { 
                title: 'Generar Reportes', 
                description: 'Haz clic aquí para exportar los datos en formato PDF o Excel.',
                side: "bottom"
            } 
        }
    ]
});

// Iniciar el recorrido interactivo
driverObj.drive();

Conclusión

Para la mayoría de los desarrollos, la recomendación principal es acudir directamente a la documentación y librería oficial de Driver.js para ejecutar todo el flujo desde el cliente. No obstante, si tu aplicación requiere generar los paneles informativos o la guía de onboarding dinámicamente desde el motor de plantillas o controladores de Laravel, este paquete resulta una alternativa sumamente práctica y bien integrada.

El código fuente completo de este ejemplo se encuentra disponible en el repositorio del libro para su consulta y revisión detallada.

https://github.com/libredesarrollo/book-course-laravel-base-package

En esta guía paso a paso descubrirás cómo conectar librerías de JavaScript con tu backend en PHP, optimizar la experiencia de usuario (UX) en tu aplicación web y personalizar ventanas emergentes para resaltar los elementos clave de tu interfaz.


Únete a la comunidad de desarrolladores que han decidido dejar de picar código y empezar a construir productos reales. Recibe mis mejores trucos de arquitectura cada semana:

Acepto recibir anuncios de interes sobre este Blog.