Ленивая загрузка

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


Конфигурация шагов для ленивой загрузки

Intro.js поддерживает динамическое определение шагов через функцию onbeforechange или при помощи массивов объектов, передаваемых в метод setOptions. Пример структуры шага:

{
    element: '#feature-button',
    intro: 'Это основной функциональный элемент.',
    position: 'right'
}

Для ленивой загрузки можно создать функцию, которая добавляет шаги по мере навигации:

let steps = [
    { element: '#step1', intro: 'Первый шаг.' }
];

const intro = introJs();
intro.setOptions({ steps: steps });

intro.onbeforechange(function(targetElement) {
    if (targetElement.id === 'step1') {
        steps.push({
            element: '#step2',
            intro: 'Второй шаг загружен динамически.',
            position: 'bottom'
        });
        intro.setOptions({ steps: steps });
    }
});

intro.start();

Здесь второй шаг создается только при переходе к первому. Это предотвращает ненужную инициализацию элементов на странице, которые еще не отображены.


Ленивое создание элементов на странице

Для сложных интерфейсов часто используется динамическое создание DOM-элементов перед добавлением шага. Например:

intro.onbeforechange(function(targetElement) {
    if (targetElement.id === 'container' && !document.querySelector('#dynamic-button')) {
        const button = document.createElement('button');
        button.id = 'dynamic-button';
        button.textContent = 'Новый функционал';
        document.querySelector('#container').appendChild(button);

        steps.push({
            element: '#dynamic-button',
            intro: 'Этот элемент появился только сейчас!',
            position: 'top'
        });
        intro.setOptions({ steps: steps });
    }
});

Это особенно важно при работе с SPA (Single Page Application), где контент часто подгружается через AJAX.


Использование onafterchange для оптимизации

Событие onafterchange позволяет инициализировать или активировать контент сразу после отображения шага. Это удобно для ленивой загрузки сложных виджетов:

intro.onafterchange(function(targetElement) {
    if (targetElement.id === 'chart-container' && !window.chartInitialized) {
        initChart('#chart-container'); // функция инициализации графика
        window.chartInitialized = true;
    }
});

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


Загрузка шагов через AJAX

В некоторых случаях контент шага полностью загружается с сервера. Для этого можно использовать асинхронные вызовы:

intro.onbeforechange(async function(targetElement) {
    if (targetElement.id === 'remote-step' && !targetElement.dataset.loaded) {
        const response = await fetch('/api/step-content');
        const data = await response.text();
        targetElement.innerHTML = data;
        targetElement.dataset.loaded = 'true';
    }
});

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


Оптимизация производительности

При ленивой загрузке стоит учитывать несколько ключевых аспектов:

  1. Минимизировать количество шагов в DOM — добавлять только необходимые элементы.
  2. Использовать onbeforechange и onafterchange для динамического создания контента.
  3. Отложенная инициализация тяжелых виджетов (графики, карты, медиаплееры) только при достижении соответствующего шага.
  4. Удаление шагов при возврате назад, если они больше не нужны, чтобы не перегружать память.

Пример комплексного ленивого интро

let steps = [
    { element: '#step1', intro: 'Начало интро.' }
];

const intro = introJs();
intro.setOptions({ steps: steps });

intro.onbeforechange(async function(targetElement) {
    if (targetElement.id === 'step1') {
        const dynamicContainer = document.createElement('div');
        dynamicContainer.id = 'step2';
        document.body.appendChild(dynamicContainer);

        const response = await fetch('/api/step2-content');
        dynamicContainer.innerHTML = await response.text();

        steps.push({
            element: '#step2',
            intro: 'Контент загружен динамически!',
            position: 'left'
        });
        intro.setOptions({ steps: steps });
    }
});

intro.start();

В этом примере шаги создаются по мере необходимости, а тяжелый контент загружается асинхронно, снижая нагрузку на страницу и ускоряя старт интро.