Интерактивные туры, реализованные с помощью Intro.js, часто прерываются пользователем: закрытие вкладки, перезагрузка страницы, переход по ссылке или намеренное завершение. В результате пользователь теряет прогресс и при повторном запуске вынужден проходить шаги заново. Это снижает удобство и может негативно повлиять на восприятие интерфейса.
Решение заключается в сохранении состояния тура и его восстановлении при повторном входе.
Intro.js не предоставляет встроенного механизма автоматического восстановления тура, однако предоставляет необходимые события и методы для реализации этой логики:
onbeforeexit — вызывается перед выходом из тураonexit — вызывается при завершенииonchange — вызывается при переходе между шагамиcurrentStep() — возвращает индекс текущего шагаgoToStep(step) — позволяет перейти к конкретному
шагуОсновная идея:
Для хранения состояния удобно использовать localStorage,
так как он сохраняет данные между перезагрузками.
const intro = introJs();
intro.onchange(function() {
const step = this.currentStep();
localStorage.setItem('introStep', step);
});
Каждый раз при переходе к новому шагу текущий индекс сохраняется.
Важно различать два сценария:
Для этого можно использовать onexit:
intro.onexit(function() {
localStorage.removeItem('introStep');
});
Такой подход очищает состояние при нормальном завершении тура.
Если требуется сохранять прогресс даже при выходе, удаление можно не выполнять.
При инициализации страницы необходимо проверить, существует ли сохранённый шаг:
const savedStep = localStorage.getItem('introStep');
if (savedStep !== null) {
intro.start().goToStep(Number(savedStep) + 1);
} else {
intro.start();
}
Особенности:
goToStepcurrentStep() возвращает 0-based индексПоэтому добавляется +1.
Возможны ситуации, когда:
Проверка:
const savedStep = parseInt(localStorage.getItem('introStep'), 10);
if (!isNaN(savedStep) && savedStep >= 0) {
intro.start().goToStep(savedStep + 1);
} else {
intro.start();
}
Если элементы тура загружаются динамически (например, через AJAX), важно убедиться, что DOM полностью готов перед восстановлением:
window.addEventListener('load', function() {
const savedStep = localStorage.getItem('introStep');
if (savedStep !== null) {
intro.start().goToStep(Number(savedStep) + 1);
}
});
В сложных случаях может понадобиться ожидание конкретного элемента:
function waitForElement(selector, callback) {
const interval = setInterval(() => {
if (document.querySelector(selector)) {
clearInterval(interval);
callback();
}
}, 100);
}
Если требуется сохранять прогресс только в рамках одной сессии (до
закрытия вкладки), вместо localStorage используется
sessionStorage:
sessionStorage.setItem('introStep', step);
Разница:
localStorage — сохраняется навсегдаsessionStorage — очищается при закрытии вкладкиВ сложных сценариях одного номера шага недостаточно. Можно сохранять:
Пример:
const state = {
step: intro.currentStep(),
url: window.location.pathname
};
localStorage.setItem('introState', JSON.stringify(state));
Восстановление:
const state = JSON.parse(localStorage.getItem('introState'));
if (state && state.url === window.location.pathname) {
intro.start().goToStep(state.step + 1);
}
Если тур охватывает несколько страниц, необходимо:
Иногда используется параметр в URL:
window.location.href = '/next-page?introStep=3';
И затем:
const params = new URLSearchParams(window.location.search);
const step = params.get('introStep');
if (step) {
intro.start().goToStep(Number(step));
}
Intro.js позволяет гибко контролировать поведение через события:
intro.onbeforechange(function(targetElement) {
// логика перед переходом
});
intro.oncomplete(function() {
localStorage.removeItem('introStep');
});
Это позволяет:
Иногда требуется принудительно сбросить прогресс:
function resetTour() {
localStorage.removeItem('introStep');
}
Или при определённых условиях:
if (userRoleChanged) {
resetTour();
}
В одностраничных приложениях (React, Vue, Angular):
Пример:
store.set('introStep', intro.currentStep());
При повторной инициализации компонента:
const step = store.get('introStep');
if (step !== null) {
intro.start().goToStep(step + 1);
}
1. Несоответствие индексов
2. Запуск до загрузки DOM
3. Отсутствие очистки состояния
4. Изменение структуры тура
Пример:
const TOUR_VERSION = 'v1';
localStorage.setItem('introVersion', TOUR_VERSION);
Проверка:
if (localStorage.getItem('introVersion') !== TOUR_VERSION) {
localStorage.removeItem('introStep');
}
const intro = introJs();
function saveStep() {
localStorage.setItem('introStep', intro.currentStep());
}
function loadStep() {
return parseInt(localStorage.getItem('introStep'), 10);
}
intro.onchange(saveStep);
intro.oncomplete(() => {
localStorage.removeItem('introStep');
});
intro.onexit(() => {
// можно оставить прогресс
});
window.addEventListener('load', () => {
const step = loadStep();
if (!isNaN(step)) {
intro.start().goToStep(step + 1);
} else {
intro.start();
}
});
Такой подход обеспечивает: