В реальных интерфейсах одного обучающего сценария недостаточно:
разные роли пользователей, различные разделы приложения и отдельные фичи
требуют независимых последовательностей шагов. В Shepherd.js это
реализуется через создание нескольких экземпляров
Shepherd.Tour, каждый из которых управляет собственным
набором шагов, состоянием и логикой переходов.
Ключевой принцип — полная изоляция туров. Каждый тур:
Каждый тур создаётся как отдельный объект:
const mainTour = new Shepherd.Tour({
defaultStepOptions: {
scrollTo: true
}
});
const settingsTour = new Shepherd.Tour({
defaultStepOptions: {
classes: 'shepherd-theme-arrows'
}
});
const onboardingT our = new Shepherd.Tour({
useModalOverlay: true
});
Важно:
Каждый тур содержит только релевантные шаги:
mainTour.addStep({
id: 'dashboard',
text: 'Это главная панель',
attachTo: {
element: '.dashboard',
on: 'bottom'
}
});
settingsTour.addStep({
id: 'profile-settings',
text: 'Настройки профиля',
attachTo: {
element: '.profile-settings',
on: 'right'
}
});
Разделение по смыслу:
Туры запускаются независимо:
mainTour.start();
settingsTour.start();
Однако важно избегать одновременного запуска нескольких туров, так как:
Часто создаётся управляющий слой:
const tours = {
main: mainTour,
settings: settingsTour,
onboarding: onboardingTour
};
function startTour(name) {
Object.values(tours).forEach(tour => tour.cancel());
tours[name].start();
}
Особенности:
Разные туры могут запускаться в зависимости от состояния приложения:
if (user.isNew) {
onboardingTour.start();
} else if (feature.isUpdated) {
featureTour.start();
}
Типичные условия:
Иногда требуется последовательный запуск:
mainTour.on('complete', () => {
settingsTour.start();
});
Или:
onboardingTour.on('cancel', () => {
console.log('Пользователь прервал onboarding');
});
Варианты связей:
Выбор тура может происходить на основе текущего маршрута:
switch (window.location.pathname) {
case '/dashboard':
mainTour.start();
break;
case '/settings':
settingsTour.start();
break;
}
В SPA-приложениях:
router.afterEach((to) => {
if (to.name === 'dashboard') {
mainTour.start();
}
});
Некоторые шаги могут быть общими для нескольких туров. Возможен вынос в фабрику:
function createHelpStep(selector, text) {
return {
text,
attachTo: {
element: selector,
on: 'bottom'
}
};
}
mainTour.addStep(createHelpStep('.help', 'Раздел помощи'));
settingsTour.addStep(createHelpStep('.help', 'Здесь помощь по настройкам'));
Преимущества:
Часто требуется запоминать, какие туры уже были показаны:
if (!localStorage.getItem('mainTourSeen')) {
mainTour.start();
localStorage.setItem('mainTourSeen', 'true');
}
Расширенный вариант:
const tourState = JSON.parse(localStorage.getItem('tours') || '{}');
if (!tourState.settings) {
settingsTour.start();
tourState.settings = true;
localStorage.setItem('tours', JSON.stringify(tourState));
}
При переходе между разделами важно корректно завершать тур:
window.addEventListener('beforeunload', () => {
Object.values(tours).forEach(tour => tour.cancel());
});
Или при смене вкладки:
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
mainTour.cancel();
}
});
Некоторые туры зависят от загрузки данных:
fetch('/api/data')
.then(() => {
dataTour.start();
});
Или ожидание DOM:
const observer = new MutationObserver(() => {
if (document.querySelector('.dynamic-element')) {
dynamicTour.start();
observer.disconnect();
}
});
observer.observe(document.body, { childList: true, subtree: true });
Основные проблемы при множественных турах:
1. Дублирование overlay
2. Конфликт фокуса
cancel() перед start()3. Перекрытие элементов
attachTo)4. Потеря состояния
Рекомендуемая организация:
/tours
mainTour.js
settingsTour.js
onboardingTour.js
index.js
Пример index.js:
import mainTour from './mainTour';
import settingsTour from './settingsTour';
export const tours = {
main: mainTour,
settings: settingsTour
};
1. Feature tours (точечные подсказки)
const featureTour = new Shepherd.Tour();
featureTour.addStep({
text: 'Новая кнопка!',
attachTo: {
element: '.new-feature',
on: 'top'
}
});
2. Контекстные туры
if (user.role === 'admin') {
adminTour.start();
}
3. Частичные туры
Тур может начинаться не с первого шага:
mainTour.show('step-3');
Полезные приёмы:
mainTour.on('show', (event) => {
console.log('Показ шага:', event.step.id);
});
mainTour.on('complete', () => {
console.log('Тур завершён');
});
При большом количестве туров:
let settingsTour;
function getSettingsTour() {
if (!settingsTour) {
settingsTour = new Shepherd.Tour();
}
return settingsTour;
}
При росте приложения:
Множественные туры становятся частью UX-стратегии:
Такой подход позволяет строить масштабируемую систему подсказок без усложнения кода и конфликтов между сценариями.