Вложенные туры представляют собой композицию нескольких независимых экземпляров тура, которые логически объединяются в единый сценарий. Такая структура применяется, когда требуется разбить сложный процесс обучения на несколько этапов, каждый из которых может запускаться, завершаться и управляться отдельно.
В библиотеке Shepherd.js отсутствует встроенное понятие «вложенного тура» как отдельной сущности. Вместо этого используется комбинация:
Shepherd.Touron, once)Это даёт гибкость, но требует явного управления состоянием.
Создание вложенной структуры начинается с объявления нескольких туров:
const mainTour = new Shepherd.Tour({
defaultStepOptions: {
cancelIcon: { enabled: true },
scrollTo: true
}
});
const subTour = new Shepherd.Tour({
defaultStepOptions: {
classes: 'shepherd-theme-dark',
scrollTo: true
}
});
Каждый тур полностью независим:
Связь между турами осуществляется вручную через события или кнопки шагов.
mainTour.addStep({
id: 'step-with-subtour',
text: 'Переход к дополнительному обучению',
buttons: [
{
text: 'Далее',
action: () => {
mainTour.hide();
subTour.start();
}
}
]
});
Ключевой момент — использование hide() вместо
complete():
hide() временно скрывает текущий турПосле завершения вложенного тура требуется восстановить основной:
subTour.on('complete', () => {
mainTour.show('next-step-id');
});
или при отмене:
subTour.on('cancel', () => {
mainTour.show('fallback-step-id');
});
Метод show(stepId) позволяет продолжить с конкретного
шага.
При работе с вложенными турами возникает необходимость отслеживать:
let currentTour = null;
function startTour(tour) {
if (currentTour) {
currentTour.hide();
}
currentTour = tour;
tour.start();
}
Допускается создание глубокой вложенности:
основной тур
вложенный тур
mainTour.addStep({
id: 'step-1',
buttons: [
{
text: 'Подтур',
action: () => {
mainTour.hide();
subTour.start();
}
}
]
});
subTour.addStep({
id: 'sub-step-1',
buttons: [
{
text: 'Ещё глубже',
action: () => {
subTour.hide();
subSubTour.start();
}
}
]
});
Возврат требует явного управления на каждом уровне.
События — основной инструмент синхронизации.
startshowhidecompletecancelmainTour.on('complete', () => {
subTour.start();
});
subTour.on('complete', () => {
finalTour.start();
});
Такой подход формирует линейный сценарий без явной вложенности в коде шагов.
Иногда вложенные туры создаются на лету в зависимости от контекста.
function createSubTour(userRole) {
const tour = new Shepherd.Tour();
if (userRole === 'admin') {
tour.addStep({ text: 'Админские функции' });
} else {
tour.addStep({ text: 'Базовые функции' });
}
return tour;
}
Это позволяет:
Для сложных сценариев требуется передавать данные:
let context = {};
mainTour.addStep({
id: 'collect-data',
buttons: [
{
text: 'Сохранить',
action: () => {
context.userChoice = 'example';
mainTour.next();
}
}
]
});
Во вложенном туре:
subTour.on('start', () => {
console.log(context.userChoice);
});
Shepherd не поддерживает одновременное отображение нескольких туров. Однако можно имитировать переключение:
Важно избегать:
start() нескольких туровИспользование complete() вместо hide():
mainTour.complete(); // невозможно вернуться
Правильный вариант:
mainTour.hide();
Если не подписаться на complete вложенного тура:
subTour.start();
// основной тур не возобновится
Если шаги разных туров ссылаются на одинаковые элементы:
Рекомендуется:
Распространённый подход:
Главный тур
├── Раздел A → Подтур A
├── Раздел B → Подтур B
└── Раздел C → Подтур C
Реализация:
function attachSubTour(mainTour, subTour, triggerStepId, returnStepId) {
mainTour.addStep({
id: triggerStepId,
buttons: [
{
text: 'Открыть',
action: () => {
mainTour.hide();
subTour.start();
}
}
]
});
subTour.on('complete', () => {
mainTour.show(returnStepId);
});
}
Иногда запуск подтура зависит от загрузки данных:
mainTour.addStep({
id: 'async-step',
buttons: [
{
text: 'Загрузить',
action: async () => {
mainTour.hide();
await fetchData();
subTour.start();
}
}
]
});
Важно учитывать:
В SPA-приложениях вложенные туры часто связаны с переходами между страницами:
mainTour.addStep({
id: 'go-to-page',
buttons: [
{
text: 'Перейти',
action: () => {
mainTour.hide();
router.push('/settings');
}
}
]
});
После загрузки страницы:
router.afterEach(() => {
subTour.start();
});
Каждый тур может иметь собственные темы:
const lightTour = new Shepherd.Tour({
defaultStepOptions: {
classes: 'shepherd-theme-light'
}
});
const darkTour = new Shepherd.Tour({
defaultStepOptions: {
classes: 'shepherd-theme-dark'
}
});
Это позволяет визуально отделять этапы обучения.
При тестировании необходимо проверять:
Частая практика — логирование:
mainTour.on('show', e => console.log('Main step:', e.step.id));
subTour.on('show', e => console.log('Sub step:', e.step.id));
Для упрощения работы создаются собственные абстракции:
class TourManager {
constructor() {
this.stack = [];
}
start(tour) {
if (this.stack.length) {
this.stack[this.stack.length - 1].hide();
}
this.stack.push(tour);
tour.start();
}
end() {
const finished = this.stack.pop();
finished.complete();
if (this.stack.length) {
this.stack[this.stack.length - 1].show();
}
}
}
Такой подход:
Вложенные туры должны:
Рекомендуется:
Большое количество туров может влиять на:
Оптимизация:
Вложенные туры в Shepherd.js — это:
Гибкость реализации позволяет строить сложные обучающие сценарии, но требует строгой дисциплины в архитектуре и управлении потоками выполнения.