Timing и триггеры

Контроль момента отображения шагов — ключевой аспект при работе с Shepherd.js. Библиотека сама по себе не навязывает строгую логику тайминга: разработчик вручную определяет, когда запускать тур, когда переходить к следующему шагу и на какие события реагировать.

Основные механизмы управления временем:

  • ручной запуск тура (tour.start())
  • программный переход между шагами (next(), back(), show())
  • ожидание появления элементов в DOM
  • синхронизация с асинхронными операциями
  • реакция на пользовательские действия

Отложенный запуск тура

Запуск тура часто должен происходить не сразу после загрузки страницы, а после выполнения определённых условий.

Простой таймаут

setTimeout(() => {
  tour.start();
}, 2000);

Используется, если интерфейс гарантированно готов через фиксированное время. Однако такой подход ненадёжен при динамической загрузке данных.


Ожидание готовности DOM

Более корректный способ — запуск после полной загрузки DOM:

document.addEventListener('DOMContentLoaded', () => {
  tour.start();
});

Если приложение использует SPA-фреймворки, этого может быть недостаточно, так как элементы могут появляться позже.


Ожидание появления элемента

Частая задача — начать тур только тогда, когда конкретный элемент доступен.

Простой polling

function waitForElement(selector, callback) {
  const interval = setInterval(() => {
    const element = document.querySelector(selector);
    if (element) {
      clearInterval(interval);
      callback(element);
    }
  }, 100);
}

waitForElement('.target', () => {
  tour.start();
});

Использование MutationObserver

Более эффективный способ отслеживания изменений DOM:

const observer = new MutationObserver(() => {
  const element = document.querySelector('.target');
  if (element) {
    observer.disconnect();
    tour.start();
  }
});

observer.observe(document.body, {
  childList: true,
  subtree: true
});

Такой подход не нагружает процессор постоянными проверками.


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

По умолчанию Shepherd автоматически переключает шаги по нажатию кнопок. Однако поведение можно полностью контролировать.

Ручной переход

tour.next();
tour.back();
tour.show('step-id');

Это позволяет:

  • синхронизировать шаги с действиями пользователя
  • ждать завершения анимаций
  • учитывать состояние приложения

Триггеры на пользовательские действия

Часто следующий шаг должен появляться только после взаимодействия пользователя.

Пример: клик по кнопке

document.querySelector('#myButton').addEventListener('click', () => {
  tour.next();
});

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


Интеграция триггеров в конфигурацию шага

Можно встроить обработчик прямо в шаг:

tour.addStep({
  id: 'step-1',
  text: 'Нажмите кнопку',
  attachTo: {
    element: '#myButton',
    on: 'bottom'
  },
  when: {
    show() {
      const button = document.querySelector('#myButton');
      button.addEventListener('click', () => {
        tour.next();
      });
    }
  }
});

Событие show срабатывает при отображении шага.


События жизненного цикла шага

Shepherd предоставляет набор хуков:

  • show — шаг показан
  • hide — шаг скрыт
  • cancel — тур отменён
  • complete — тур завершён

Пример использования

tour.on('show', (event) => {
  console.log('Показ шага:', event.step.id);
});

Ожидание асинхронных операций

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

fetch('/api/data')
  .then(response => response.json())
  .then(data => {
    renderUI(data);
    tour.start();
  });

Асинхронная логика внутри шага

tour.addStep({
  id: 'async-step',
  text: 'Загрузка данных...',
  when: {
    show: async function () {
      await loadData();
      tour.next();
    }
  }
});

Важно: пользователь может не увидеть текст шага, если переход происходит слишком быстро.


Блокировка перехода до выполнения условий

Можно предотвратить переход, если условие не выполнено.

tour.addStep({
  id: 'validation-step',
  text: 'Введите данные',
  buttons: [
    {
      text: 'Далее',
      action: () => {
        const value = document.querySelector('#input').value;
        if (value) {
          tour.next();
        } else {
          alert('Введите значение');
        }
      }
    }
  ]
});

Работа с динамическими интерфейсами

В SPA-приложениях элементы могут:

  • появляться с задержкой
  • изменяться
  • удаляться

Проверка перед показом шага

tour.addStep({
  id: 'dynamic-step',
  attachTo: {
    element: '.dynamic-element',
    on: 'right'
  },
  beforeShowPromise() {
    return new Promise(resolve => {
      waitForElement('.dynamic-element', resolve);
    });
  }
});

beforeShowPromise — ключевой инструмент синхронизации.


beforeShowPromise — основной механизм тайминга

Позволяет отложить показ шага до выполнения асинхронного кода.

tour.addStep({
  id: 'step',
  text: 'Ожидание...',
  beforeShowPromise: () => {
    return new Promise(resolve => {
      setTimeout(resolve, 1000);
    });
  }
});

Применение:

  • ожидание DOM
  • ожидание API
  • завершение анимаций

Синхронизация с анимациями

Если интерфейс содержит анимации, важно дождаться их завершения.

tour.addStep({
  id: 'animated-step',
  beforeShowPromise: () => {
    return new Promise(resolve => {
      const element = document.querySelector('.animated');
      element.addEventListener('transitionend', resolve, { once: true });
    });
  }
});

Комбинирование нескольких условий

beforeShowPromise: () => {
  return Promise.all([
    waitForData(),
    waitForAnimation()
  ]);
}

Автоматические триггеры на основе событий

Можно запускать тур или шаги при:

  • первом входе пользователя
  • достижении определённого состояния
  • изменении URL

Пример: запуск при изменении маршрута

window.addEventListener('hashchange', () => {
  if (location.hash === '#dashboard') {
    tour.start();
  }
});

Условные триггеры

if (!localStorage.getItem('tourShown')) {
  tour.start();
  localStorage.setItem('tourShown', 'true');
}

Позволяет показывать тур только один раз.


Пауза и продолжение тура

Shepherd не имеет встроенной “паузы”, но можно реализовать её вручную:

let currentStepId;

tour.on('show', (event) => {
  currentStepId = event.step.id;
});

// позже
tour.show(currentStepId);

Ошибки тайминга

Наиболее распространённые проблемы:

1. Шаг показывается до появления элемента

  • решение: beforeShowPromise

2. Элемент исчезает

  • проверка перед показом

3. Слишком быстрые переходы

  • добавление задержек

4. Дублирование событий

  • использование { once: true } в обработчиках

Лучшие практики

  • не использовать фиксированные таймауты без необходимости
  • всегда учитывать асинхронность интерфейса
  • использовать beforeShowPromise как основной инструмент
  • привязывать шаги к реальным действиям пользователя
  • минимизировать автоматические переходы

Архитектурный подход

Для крупных приложений рекомендуется:

  • вынести логику ожиданий в отдельные утилиты
  • централизовать триггеры
  • использовать состояние приложения (state management)

Пример:

function startTourWhenReady() {
  return Promise.all([
    waitForUserData(),
    waitForUIRender()
  ]).then(() => {
    tour.start();
  });
}

Сценарии использования

Онбординг:

  • запуск при первом входе
  • переход по кликам

Обучение функциям:

  • триггеры на действия
  • блокировка переходов

Подсказки:

  • запуск при наведении или клике
  • короткие цепочки шагов

Грамотное управление таймингом превращает тур из формальной подсказки в интерактивный инструмент обучения, который синхронизирован с поведением пользователя и состоянием интерфейса.