Интеграция с роутингом

Intro.js предоставляет удобный механизм создания интерактивных туров по интерфейсу, но при работе с современными SPA (Single Page Application) с роутингом возникает необходимость управлять переходами между страницами или компонентами во время тура. Для этого необходимо правильно интегрировать библиотеку с системой роутинга.


Основные принципы интеграции

  1. Отложенный запуск шагов В SPA переход между маршрутами может происходить асинхронно. Если шаг тура ссылается на элемент, который ещё не был отрендерен, Intro.js выдаст ошибку. Решение — запускать шаги после полной загрузки целевого компонента. Это достигается через события роутера или промисы.

  2. Привязка шагов к маршрутам Каждый шаг тура можно ассоциировать с конкретным маршрутом. Структура шага включает ключевые параметры:

    const steps = [
        {
            element: '#step1',
            intro: 'Это первый элемент на главной странице',
            route: '/' // маршрут, где элемент присутствует
        },
        {
            element: '#step2',
            intro: 'Это кнопка настроек',
            route: '/settings'
        }
    ];

    В данном случае свойство route — пользовательское поле, которое поможет определить, нужно ли менять маршрут перед отображением шага.


Управление переходами между маршрутами

Для корректной работы тура с несколькими страницами необходимо внедрить логику, которая:

  1. Проверяет текущий маршрут.
  2. Если маршрут не совпадает с ожидаемым для текущего шага, инициирует переход через роутер.
  3. Ждет завершения рендеринга целевого компонента.
  4. Запускает Intro.js на текущем шаге.

Пример интеграции с Vue Router:

import introJs from 'intro.js';
import router from './router';

let currentStep = 0;

function startTour(steps) {
    const intro = introJs();
    
    function showStep(stepIndex) {
        const step = steps[stepIndex];
        
        if (router.currentRoute.value.path !== step.route) {
            router.push(step.route).then(() => {
                // Ждем рендеринга компонента
                setTimeout(() => intro.setOptions({ steps: [step] }).start(), 300);
            });
        } else {
            intro.setOptions({ steps: [step] }).start();
        }
    }

    intro.oncomplete(() => console.log('Тур завершен'));
    intro.onexit(() => console.log('Тур прерван'));

    showStep(currentStep);
}

Ключевой момент — использование отложенного вызова setTimeout или реактивного отслеживания рендера для уверенности, что элемент уже существует в DOM.


Поддержка нескольких маршрутов в одном туре

Для туров, которые проходят через несколько страниц, можно использовать события onbeforechange и onafterchange Intro.js:

intro.onbeforechange(function(targetElement) {
    const stepIndex = intro._currentStep;
    const step = steps[stepIndex];

    if (router.currentRoute.value.path !== step.route) {
        intro.exit(); // Прерываем тур
        router.push(step.route).then(() => {
            setTimeout(() => startTourFromStep(stepIndex), 300);
        });
    }
});

function startTourFromStep(index) {
    currentStep = index;
    startTour(steps);
}

Такой подход позволяет плавно перемещаться между страницами, сохраняя контекст тура.


Асинхронная подгрузка контента

Если элементы для тура загружаются динамически, необходимо убедиться, что Intro.js не пытается показать шаг до их появления. Возможные решения:

  • Использовать MutationObserver для отслеживания появления элемента:
function waitForElement(selector) {
    return new Promise(resolve => {
        const el = document.querySelector(selector);
        if (el) return resolve(el);

        const observer = new MutationObserver(() => {
            const el = document.querySelector(selector);
            if (el) {
                observer.disconnect();
                resolve(el);
            }
        });
        observer.observe(document.body, { childList: true, subtree: true });
    });
}
  • Комбинировать с роутером:
router.push('/settings').then(() => {
    waitForElement('#step2').then(() => {
        introJs().setOptions({ steps: [steps[1]] }).start();
    });
});

Сохранение состояния тура

Для туров через маршруты полезно хранить текущий шаг в глобальном состоянии (Vuex, Pinia, Redux) или в localStorage:

localStorage.setItem('tourStep', currentStep);

// При повторном открытии страницы
const savedStep = parseInt(localStorage.getItem('tourStep'), 10) || 0;
startTourFromStep(savedStep);

Это позволяет прерывать и возобновлять тур на любом шаге, независимо от переходов между страницами.


Советы по оптимизации

  • Минимизировать количество повторных инициализаций Intro.js при переходах.
  • Использовать динамическое построение массива шагов для каждого маршрута, чтобы исключить шаги, не относящиеся к текущей странице.
  • Планировать тур с учетом асинхронной загрузки данных, чтобы шаги не зависели от элементов, которые могут появиться позже.

Такой подход обеспечивает плавную интеграцию Intro.js с роутером, позволяет создавать комплексные многостраничные туры и поддерживать корректное отображение интерактивных шагов даже при динамически загружаемом контенте.