Сохранение прогресса пользователя

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

Сохранение прогресса позволяет:

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

Общая стратегия хранения состояния

Intro.js не предоставляет встроенного механизма сохранения прогресса, поэтому решение строится на внешнем хранилище. Основные варианты:

  • localStorage — для хранения в браузере
  • sessionStorage — для текущей сессии
  • cookies — для совместимости
  • сервер (API, база данных) — для авторизованных пользователей

Чаще всего используется localStorage, так как он прост и не требует серверной логики.

Сохранение текущего шага

Intro.js предоставляет события, позволяющие отслеживать переходы между шагами. Основное — onchange.

Пример сохранения текущего шага:

const intro = introJs();

intro.onchange(function (targetElement) {
  const step = intro._currentStep;
  localStorage.setItem('introStep', step);
});

intro.start();

Здесь:

  • intro._currentStep — текущий индекс шага
  • значение сохраняется при каждом переходе

Восстановление прогресса

При повторной инициализации тура необходимо извлечь сохранённый шаг и установить его.

const savedStep = parseInt(localStorage.getItem('introStep'), 10);

const intro = introJs();

if (!isNaN(savedStep)) {
  intro.goToStep(savedStep + 1);
}

intro.start();

Особенности:

  • goToStep использует нумерацию с 1
  • сохранённый индекс (с 0) увеличивается на 1

Завершение тура и очистка данных

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

intro.oncomplete(function () {
  localStorage.removeItem('introStep');
});

intro.onexit(function () {
  localStorage.removeItem('introStep');
});

События:

  • oncomplete — пользователь прошёл тур до конца
  • onexit — пользователь вышел вручную

Сохранение факта прохождения

Иногда достаточно не хранить шаг, а просто запомнить, что тур уже был пройден.

if (!localStorage.getItem('introCompleted')) {
  introJs().start();
}

При завершении:

intro.oncomplete(function () {
  localStorage.setItem('introCompleted', 'true');
});

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

Частичное прохождение и ветвление

В более сложных сценариях требуется учитывать:

  • какие шаги уже были показаны
  • какие пропущены
  • разные сценарии для разных ролей

В этом случае сохраняется не один индекс, а структура:

const progress = {
  currentStep: 2,
  completedSteps: [0, 1],
  skippedSteps: [3]
};

localStorage.setItem('introProgress', JSON.stringify(progress));

Восстановление:

const progress = JSON.parse(localStorage.getItem('introProgress'));

if (progress) {
  intro.goToStep(progress.currentStep + 1);
}

Работа с динамическими элементами

Если шаги привязаны к элементам, которые могут отсутствовать (например, из-за условий интерфейса), восстановление может ломаться.

Решения:

  • проверять наличие элемента перед переходом
  • пересчитывать шаги
  • использовать onbeforechange для контроля
intro.onbeforechange(function (targetElement) {
  if (!targetElement) {
    intro.nextStep();
  }
});

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

Для авторизованных пользователей прогресс часто сохраняется на сервере.

Пример:

intro.onchange(function () {
  fetch('/api/save-progress', {
    method: 'POST',
    body: JSON.stringify({
      step: intro._currentStep
    }),
    headers: {
      'Content-Type': 'application/json'
    }
  });
});

При загрузке страницы:

fetch('/api/get-progress')
  .then(res => res.json())
  .then(data => {
    if (data.step !== undefined) {
      intro.goToStep(data.step + 1).start();
    }
  });

Преимущества:

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

Обработка обновления страницы

При перезагрузке страницы важно:

  1. сохранить текущий шаг
  2. автоматически восстановить тур

Комбинация:

window.addEventListener('beforeunload', function () {
  localStorage.setItem('introStep', intro._currentStep);
});

И при загрузке:

window.onl oad = function () {
  const step = localStorage.getItem('introStep');

  if (step !== null) {
    introJs().goToStep(parseInt(step) + 1).start();
  }
};

Ограничение повторного показа

Иногда тур должен показываться:

  • только один раз
  • или раз в определённый период

Пример с таймером:

const lastShown = localStorage.getItem('introLastShown');
const now = Date.now();

if (!lastShown || now - lastShown > 7 * 24 * 60 * 60 * 1000) {
  introJs().start();
  localStorage.setItem('introLastShown', now);
}

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

Минимизировать навязчивость

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

Сохранять только необходимое

  • один шаг или флаг завершения
  • избегать лишних данных

Учитывать изменения интерфейса

  • при обновлении UI старые шаги могут стать невалидными

Обрабатывать ошибки

  • проверять наличие сохранённых данных
  • корректно работать при их отсутствии

Использовать именование ключей

intro_step_main_page
intro_completed_dashboard

Это помогает избежать конфликтов при нескольких турах.

Расширенные сценарии

Несколько туров в приложении

localStorage.setItem('intro_dashboard_step', step);
localStorage.setItem('intro_profile_completed', true);

Каждый тур имеет собственное состояние.

Роли пользователей

const role = 'admin';
localStorage.setItem(`intro_${role}_step`, step);

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

Прогресс с привязкой к версии приложения

const version = '1.2.0';
localStorage.setItem('introVersion', version);

Если версия изменилась — тур можно запустить заново.

if (localStorage.getItem('introVersion') !== version) {
  introJs().start();
}