Привязка к элементам через attachTo

Ключевая возможность Shepherd.js — позиционирование шагов тура относительно конкретных элементов интерфейса. Это реализуется через свойство attachTo, которое определяет, к какому элементу будет «прикреплён» попап и с какой стороны он появится.


Базовый синтаксис attachTo

tour.addStep({
  id: 'example-step',
  text: 'Описание шага',
  attachTo: {
    element: '.my-button',
    on: 'bottom'
  }
});

Состав:

  • element — селектор DOM-элемента или сам элемент
  • on — позиция относительно элемента

Позиции (on)

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

Значение Описание
top Сверху
bottom Снизу
left Слева
right Справа
top-start / top-end Сверху с выравниванием
bottom-start / bottom-end Снизу с выравниванием

Пример:

attachTo: {
  element: '#menu',
  on: 'right'
}

Использование DOM-элемента напрямую

Вместо строки-селектора можно передать реальный DOM-узел:

const button = document.querySelector('.submit-btn');

tour.addStep({
  text: 'Нажмите эту кнопку',
  attachTo: {
    element: button,
    on: 'top'
  }
});

Динамическое определение элемента

element может быть функцией, возвращающей элемент. Это полезно, если элемент появляется динамически:

attachTo: {
  element: () => document.querySelector('.dynamic-item'),
  on: 'bottom'
}

Такой подход позволяет Shepherd искать элемент в момент показа шага, а не при создании тура.


Поведение при отсутствии элемента

Если элемент не найден:

  • шаг может не отобразиться
  • либо появится в центре экрана (в зависимости от конфигурации)

Контроль осуществляется через параметры:

tour.addStep({
  text: 'Шаг без элемента',
  attachTo: {
    element: '.unknown',
    on: 'top'
  },
  when: {
    show() {
      if (!document.querySelector('.unknown')) {
        this.cancel();
      }
    }
  }
});

Смещение и точная настройка позиции

Shepherd использует Popper.js для позиционирования, что позволяет тонко управлять размещением через popperOptions:

tour.addStep({
  text: 'Смещение шага',
  attachTo: {
    element: '.target',
    on: 'bottom'
  },
  popperOptions: {
    modifiers: [
      {
        name: 'offset',
        options: {
          offset: [0, 10]
        }
      }
    ]
  }
});

Пояснение:

  • [0, 10] — смещение по X и Y

Привязка к скрытым или анимированным элементам

Если элемент:

  • скрыт (display: none)
  • или появляется через анимацию

важно синхронизировать показ шага:

when: {
  show: () => {
    const el = document.querySelector('.animated');
    if (el) {
      el.classList.add('visible');
    }
  }
}

В сложных случаях используется beforeShowPromise:

tour.addStep({
  text: 'Ждём появления элемента',
  attachTo: {
    element: '.delayed',
    on: 'bottom'
  },
  beforeShowPromise: () => {
    return new Promise(resolve => {
      const interval = setInterval(() => {
        if (document.querySelector('.delayed')) {
          clearInterval(interval);
          resolve();
        }
      }, 100);
    });
  }
});

Центрирование шага (без attachTo)

Если attachTo не указан:

tour.addStep({
  text: 'Общий шаг без привязки'
});

Шаг отображается по центру экрана. Это удобно для вводных или финальных сообщений.


Работа с несколькими совпадающими элементами

Если селектор находит несколько элементов:

attachTo: {
  element: '.item',
  on: 'top'
}

Shepherd выберет первый найденный элемент.

Для точного выбора:

document.querySelectorAll('.item')[2]

Прокрутка к элементу

Shepherd автоматически прокручивает страницу до элемента, если он вне видимой области. Это поведение настраивается:

scrollTo: true

Или более точно:

scrollTo: {
  beh * avior: 'smooth',
  block: 'center'
}

Привязка к элементам внутри контейнеров

Если элемент находится внутри прокручиваемого контейнера:

  • важно, чтобы контейнер был правильно рассчитан в layout
  • иногда требуется ручная прокрутка контейнера

Пример:

beforeShowPromise: () => {
  return new Promise(resolve => {
    const container = document.querySelector('.scroll-container');
    const item = container.querySelector('.target');

    container.scrollTop = item.offsetTop;
    resolve();
  });
}

Частые проблемы и особенности

1. Неправильный селектор

element: 'button' // слишком общий

Рекомендуется использовать уникальные селекторы (id, data-атрибуты).


2. Элемент вне DOM на момент инициализации

Решение: использовать функцию или beforeShowPromise.


3. Перекрытие другими элементами

Иногда tooltip оказывается под другими слоями. Решение:

.shepherd-element {
  z-index: 9999;
}

4. Неправильное позиционирование

Причины:

  • CSS transform у родителя
  • overflow: hidden

Popper.js может некорректно рассчитывать позицию в таких условиях.


Практический пример

const tour = new Shepherd.Tour({
  defaultStepOptions: {
    scrollTo: true
  }
});

tour.addStep({
  id: 'start',
  text: 'Это кнопка запуска',
  attachTo: {
    element: '#start-btn',
    on: 'bottom'
  },
  buttons: [
    {
      text: 'Далее',
      action: tour.next
    }
  ]
});

tour.addStep({
  id: 'menu',
  text: 'Это меню навигации',
  attachTo: {
    element: '.nav-menu',
    on: 'right'
  }
});

Рекомендации по использованию

  • использовать стабильные селекторы (data-tour, id)
  • избегать привязки к временным элементам
  • учитывать адаптивность интерфейса
  • проверять поведение на разных разрешениях
  • использовать beforeShowPromise для асинхронных сценариев

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

Условная привязка:

attachTo: {
  element: () => {
    if (window.innerWidth < 768) {
      return document.querySelector('.mobile-menu');
    }
    return document.querySelector('.desktop-menu');
  },
  on: 'bottom'
}

Изменение позиции на лету:

when: {
  show() {
    const step = this;
    const isMobile = window.innerWidth < 768;

    step.updateStepOptions({
      attachTo: {
        element: '.target',
        on: isMobile ? 'bottom' : 'right'
      }
    });
  }
}

Привязка через attachTo — фундаментальная часть Shepherd.js, определяющая контекст каждого шага тура и его визуальную связь с интерфейсом. Грамотное использование этого механизма напрямую влияет на удобство и понятность пользовательского обучения.