Рендеринг кастомного контента

Библиотека Shepherd.js предоставляет гибкие механизмы для формирования содержимого шагов (steps), позволяя использовать не только простой текст, но и сложные HTML-структуры, динамически создаваемые элементы и даже компоненты UI-фреймворков.

Ключевым элементом является свойство text, которое принимает:

  • строку
  • HTML-разметку
  • DOM-элемент
  • функцию, возвращающую любой из перечисленных вариантов

Это открывает возможности для полного контроля над отображаемым содержимым.


Использование HTML-разметки

Наиболее простой способ кастомизации — передача HTML-строки:

tour.addStep({
  id: 'example-step',
  text: `
    <div class="custom-content">
      <h3>Заголовок шага</h3>
      <p>Подробное описание действия</p>
    </div>
  `,
  attachTo: {
    element: '.target-element',
    on: 'bottom'
  }
});

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

  • HTML вставляется напрямую в DOM
  • допускается использование классов, inline-стилей и вложенных элементов
  • необходимо учитывать безопасность (XSS при динамических данных)

Передача DOM-элемента

Для более сложных сценариев можно передать заранее созданный DOM-узел:

const content = document.createElement('div');
content.innerHTML = `
  <h3>Динамический блок</h3>
  <button id="action-btn">Нажать</button>
`;

tour.addStep({
  id: 'dom-step',
  text: content,
  attachTo: {
    element: '.target',
    on: 'right'
  }
});

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

  • возможность навешивания обработчиков событий
  • переиспользование элементов
  • интеграция с существующим DOM

Генерация контента через функцию

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

tour.addStep({
  id: 'dynamic-step',
  text: () => {
    const el = document.createElement('div');
    el.textContent = `Текущее время: ${new Date().toLocaleTimeString()}`;
    return el;
  },
  attachTo: {
    element: '.clock',
    on: 'top'
  }
});

Ключевые моменты:

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

Кастомные шаблоны через when и жизненный цикл

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

tour.addStep({
  id: 'lifecycle-step',
  text: '<div id="content-area"></div>',
  when: {
    show: () => {
      const container = document.getElementById('content-area');
      container.innerHTML = '<strong>Контент загружен при показе</strong>';
    }
  }
});

Доступные события:

  • show — при отображении
  • hide — при скрытии
  • before-show — перед показом

Интеграция с шаблонизаторами

Использование шаблонизаторов (например, Handlebars, Mustache) упрощает генерацию сложного контента:

const template = Handlebars.compile(`
  <div>
    <h3>{{title}}</h3>
    <p>{{description}}</p>
  </div>
`);

tour.addStep({
  id: 'template-step',
  text: template({
    title: 'Заголовок',
    description: 'Описание шага'
  })
});

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

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

Интеграция с фреймворками (React, Vue)

Для SPA-приложений контент можно рендерить через компоненты.

React

import ReactDOM from 'react-dom';

tour.addStep({
  id: 'react-step',
  text: () => {
    const container = document.createElement('div');
    ReactDOM.render(<MyComponent />, container);
    return container;
  }
});

Vue

import { createApp } from 'vue';

tour.addStep({
  id: 'vue-step',
  text: () => {
    const container = document.createElement('div');
    createApp(MyComponent).mount(container);
    return container;
  }
});

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

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

Добавление интерактивных элементов

Контент шага может включать кнопки, формы и другие элементы:

tour.addStep({
  id: 'interactive-step',
  text: () => {
    const wrapper = document.createElement('div');

    const button = document.createElement('button');
    button.textContent = 'Действие';

    button.addEventListener('click', () => {
      alert('Кнопка нажата');
    });

    wrapper.appendChild(button);
    return wrapper;
  }
});

Рекомендации:

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

Кастомизация контейнера шага

Shepherd позволяет управлять внешним видом контейнера через classes:

tour.addStep({
  id: 'styled-step',
  text: 'Стилизованный контент',
  classes: 'my-custom-step'
});

CSS:

.my-custom-step {
  background: #222;
  color: #fff;
  border-radius: 10px;
}

Использование beforeShowPromise для асинхронного контента

При необходимости загрузки данных перед показом:

tour.addStep({
  id: 'async-step',
  text: 'Загрузка...',
  beforeShowPromise: () => {
    return fetch('/api/data')
      .then(res => res.json())
      .then(data => {
        document.querySelector('.shepherd-text').innerHTML =
          `<pre>${JSON.stringify(data, null, 2)}</pre>`;
      });
  }
});

Назначение:

  • ожидание данных перед отображением
  • предотвращение показа пустого контента

Управление структурой шага

Помимо text, можно настраивать:

  • title — заголовок
  • buttons — массив кнопок
  • footer (через кастомный HTML)

Пример:

tour.addStep({
  id: 'full-step',
  title: 'Заголовок',
  text: '<p>Описание</p>',
  buttons: [
    {
      text: 'Далее',
      action: tour.next
    }
  ]
});

Работа с пользовательскими стилями и анимацией

Контент можно дополнять CSS-анимациями:

.fade-in {
  animation: fadeIn 0.5s ease-in;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}
text: '<div class="fade-in">Анимированный блок</div>'

Ограничения и нюансы

  • Shepherd не изолирует стили (нет Shadow DOM)
  • возможны конфликты CSS
  • важно контролировать жизненный цикл DOM-элементов
  • при повторном использовании элементов необходимо избегать дублирования

Архитектурные рекомендации

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

Пример комплексного шага

function createStepContent(user) {
  const container = document.createElement('div');

  container.innerHTML = `
    <h3>${user.name}</h3>
    <p>Email: ${user.email}</p>
  `;

  const button = document.createElement('button');
  button.textContent = 'Подробнее';

  button.addEventListener('click', () => {
    console.log(user);
  });

  container.appendChild(button);

  return container;
}

tour.addStep({
  id: 'user-step',
  text: () => createStepContent({
    name: 'Иван',
    email: 'ivan@example.com'
  })
});

Такой подход обеспечивает гибкость, масштабируемость и чистоту архитектуры при работе с кастомным контентом в Shepherd.js.