Отслеживание изменений в DOM

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

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

Для динамических страниц Intro.js тесно интегрируется с нативным механизмом JavaScript — MutationObserver. Этот объект позволяет реагировать на изменения DOM, такие как добавление новых узлов, удаление узлов или изменение атрибутов элементов.

Пример создания наблюдателя:

const observer = new MutationObserver((mutationsList, observer) => {
    for (const mutation of mutationsList) {
        if (mutation.type === 'childList') {
            console.log('Добавлены или удалены элементы');
        }
        if (mutation.type === 'attributes') {
            console.log(`Изменён атрибут ${mutation.attributeName}`);
        }
    }
});

observer.observe(document.body, {
    childList: true, 
    subtree: true, 
    attributes: true
});
  • childList: true — отслеживание добавления и удаления дочерних узлов.
  • subtree: true — включает наблюдение за всеми вложенными элементами.
  • attributes: true — отслеживание изменений атрибутов элементов.

Это позволяет Intro.js корректно обновлять шаги, когда целевые элементы появляются динамически.

Динамическое обновление шагов Intro.js

Когда элемент, на который ссылается шаг, появляется после инициализации Intro.js, необходимо обновить шаги. Это делается методом refresh():

introJs().start();
introJs().refresh();

Метод refresh() пересчитывает позиции всех элементов тура, что особенно важно при динамических изменениях страницы.

Использование callback-функций для динамических шагов

Intro.js предоставляет несколько событий, которые можно использовать для синхронизации с DOM:

  • onbeforechange(targetElement) — вызывается перед переходом к новому шагу. Можно использовать для проверки существования элемента.
  • onchange(targetElement) — вызывается при активации нового шага, подходит для подгрузки данных или отображения скрытых элементов.
  • oncomplete() — срабатывает при завершении тура.

Пример динамического добавления шага:

const intro = introJs();

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

intro.onbeforechange(function(targetElement) {
    if (!document.querySelector('#step2')) {
        const newStep = document.createElement('div');
        newStep.id = 'step2';
        document.body.appendChild(newStep);
        intro.addStep({
            element: '#step2',
            intro: 'Второй шаг добавлен динамически'
        });
        intro.refresh();
    }
});

intro.start();

Этот подход гарантирует, что интерактивный тур не «сломается», если шаг появляется позже.

Отслеживание изменений контента внутри шагов

Даже если элемент существует, его содержимое может изменяться, что влияет на подсказки и визуальное оформление. Использование MutationObserver позволяет автоматически корректировать Intro.js при изменении текста, классов или других атрибутов:

const targetNode = document.querySelector('#dynamicContent');

const contentObserver = new MutationObserver(() => {
    introJs().refresh();
});

contentObserver.observe(targetNode, {
    childList: true,
    subtree: true,
    characterData: true
});
  • characterData: true — отслеживание изменений текста внутри узлов.
  • Повторный вызов refresh() обеспечивает правильное позиционирование подсказок.

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

Отслеживание всех изменений DOM может быть ресурсоёмким. Рекомендуется:

  • Ограничивать область наблюдения (observer.observe), вместо использования всего document.body.
  • Не отслеживать изменения, которые не влияют на тур.
  • Использовать дебаунс-методы при частых изменениях, чтобы избежать многократных вызовов refresh():
let refreshTimeout;
const debouncedRefresh = () => {
    clearTimeout(refreshTimeout);
    refreshTimeout = setTimeout(() => introJs().refresh(), 100);
};

observer = new MutationObserver(debouncedRefresh);

Интеграция с SPA и фреймворками

Для приложений на React, Vue или Angular шаги Intro.js часто должны синхронизироваться с состоянием компонентов. Основные принципы:

  1. Инициализировать Intro.js после рендеринга компонента.
  2. Использовать MutationObserver или lifecycle hooks (например, componentDidUpdate в React) для обновления шагов.
  3. Применять refresh() каждый раз, когда DOM компоненты изменяются.

Вывод

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