Использование с курсором мыши

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

Создание кастомного источника позиции

Popper.js по умолчанию принимает в качестве reference DOM-элемент. Чтобы использовать координаты курсора, необходимо создать объект с полями getBoundingClientRect и contextElement. Например:

const virtualElement = {
  getBoundingClientRect: () => ({
    width: 0,
    height: 0,
    top: mouseEvent.clientY,
    bottom: mouseEvent.clientY,
    left: mouseEvent.clientX,
    right: mouseEvent.clientX,
  }),
  contextElement: document.body
};
  • width и height устанавливаются в 0, так как позиция привязывается именно к точке курсора.
  • top, bottom, left, right задаются по координатам события мыши mouseEvent.clientX и mouseEvent.clientY.
  • contextElement указывает контекст для вычислений — обычно это document.body.

Инициализация Popper

Popper создается стандартным методом createPopper, но с виртуальным элементом:

const popperInstance = Popper.createPopper(virtualElement, tooltipElement, {
  placement: 'top',
  modifiers: [
    {
      name: 'offset',
      options: {
        offset: [0, 8] // сдвиг тултипа от курсора
      }
    },
    {
      name: 'preventOverflow',
      options: {
        padding: 5
      }
    }
  ]
});

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

  • placement — начальное направление всплывающего элемента относительно курсора.
  • Модификатор offset позволяет добавить небольшой отступ, чтобы элемент не перекрывал сам курсор.
  • preventOverflow гарантирует, что тултип не выйдет за пределы видимой области.

Обновление позиции при движении мыши

Чтобы тултип следовал за курсором, необходимо динамически обновлять reference:

document.addEventListener('mousemove', (event) => {
  virtualElement.getBoundingClientRect = () => ({
    width: 0,
    height: 0,
    top: event.clientY,
    bottom: event.clientY,
    left: event.clientX,
    right: event.clientX,
  });

  popperInstance.update();
});
  • update() пересчитывает позицию Popper с учетом новых координат.
  • Изменение функции getBoundingClientRect позволяет Popper использовать актуальное положение мыши.

Работа с динамическими модификаторами

Popper.js поддерживает кастомные модификаторы, которые позволяют выполнять дополнительные вычисления при обновлении позиции. Например, можно изменить placement в зависимости от положения курсора на экране:

const followCursorModifier = {
  name: 'followCursorPlacement',
  enabled: true,
  phase: 'main',
  fn({ state }) {
    if (state.rects.reference.top < 100) {
      state.placement = 'bottom';
    } else {
      state.placement = 'top';
    }
  }
};

Popper.createPopper(virtualElement, tooltipElement, {
  modifiers: [followCursorModifier]
});
  • phase определяет момент вызова функции модификатора.
  • state.rects.reference содержит текущее положение виртуального элемента.
  • Изменение state.placement динамически изменяет направление тултипа.

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

При использовании события mousemove стоит учитывать частоту обновлений:

  • Использовать requestAnimationFrame для синхронизации с рендером:
let rafId;

document.addEventListener('mousemove', (event) => {
  if (rafId) cancelAnimationFrame(rafId);
  rafId = requestAnimationFrame(() => {
    virtualElement.getBoundingClientRect = () => ({
      width: 0,
      height: 0,
      top: event.clientY,
      bottom: event.clientY,
      left: event.clientX,
      right: event.clientX,
    });
    popperInstance.update();
  });
});
  • Ограничение вызовов update() предотвращает падение FPS при интенсивном движении мыши.
  • Можно дополнительно комбинировать с throttle или debounce для управления частотой.

Интеграция с визуальными эффектами

Попперы, привязанные к курсору, часто используют анимации для плавного появления:

.tooltip {
  transition: transform 0.1s ease, opacity 0.1s ease;
  opacity: 0;
  transform: scale(0.9);
}

.tooltip.show {
  opacity: 1;
  transform: scale(1);
}
  • При срабатывании mouseenter добавляется класс show, при mouseleave удаляется.
  • Поппер обновляется перед отображением, чтобы позиция была точной.

Обработка границ экрана

Даже с preventOverflow стоит учитывать углы экрана:

  • Использовать flip модификатор для автоматической смены стороны:
{
  name: 'flip',
  options: {
    fallbackPlacements: ['top', 'bottom', 'right', 'left']
  }
}
  • Комбинация offset, preventOverflow и flip обеспечивает корректное позиционирование на всех разрешениях.

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

  1. Создание виртуального элемента для курсора.
  2. Инициализация Popper с базовым placement и модификаторами.
  3. Обработчик mousemove с обновлением getBoundingClientRect и вызовом update().
  4. Использование CSS-анимаций для плавного появления тултипа.
  5. Модификаторы flip и preventOverflow для корректного позиционирования на краях экрана.

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