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

Базовый сценарий использования Popper.js — позиционирование всплывающих подсказок (tooltip) относительно целевого элемента.

HTML-разметка:

<button id="button">Наведи курсор</button>
<div id="tooltip" role="tooltip">
  Подсказка
  <div id="arrow" data-popper-arrow></div>
</div>

CSS (минимально необходимый):

#tooltip {
  position: absolute;
  background: black;
  color: white;
  padding: 8px;
  border-radius: 4px;
  font-size: 12px;
}

#arrow {
  width: 8px;
  height: 8px;
  background: black;
  position: absolute;
  transform: rotate(45deg);
}

Jav * aScript:

import { createPopper } from '@popperjs/core';

const button = document.querySelector('#button');
const tooltip = document.querySelector('#tooltip');

const popperInstance = createPopper(button, tooltip, {
  placement: 'top',
  modifiers: [
    {
      name: 'arrow',
      options: {
        element: document.querySelector('#arrow'),
      },
    },
  ],
});

Управление показом:

function show() {
  tooltip.setAttribute('data-show', '');
  popperInstance.update();
}

function hide() {
  tooltip.removeAttribute('data-show');
}

button.addEventListener('mouseenter', show);
button.addEventListener('mouseleave', hide);

Динамическое изменение позиции

Popper автоматически пересчитывает положение при изменении размеров окна или контента.

Пример ручного обновления:

window.addEventListener('resize', () => {
  popperInstance.update();
});

Изменение позиции во время выполнения:

popperInstance.setOptions({
  placement: 'right',
});

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

Модификаторы — ключевой механизм настройки поведения Popper.

offset

Добавляет отступ между элементами:

createPopper(button, tooltip, {
  modifiers: [
    {
      name: 'offset',
      options: {
        offset: [0, 10],
      },
    },
  ],
});

flip

Меняет сторону при нехватке места:

{
  name: 'flip',
  options: {
    fallbackPlacements: ['bottom', 'right'],
  },
}

preventOverflow

Предотвращает выход за границы контейнера:

{
  name: 'preventOverflow',
  options: {
    boundary: document.body,
  },
}

Контекстное меню (правый клик)

Создание кастомного контекстного меню:

HTML:

<div id="area">Правая кнопка мыши</div>
<div id="menu">Меню</div>

Jav * aScript:

const area = document.querySelector('#area');
const menu = document.querySelector('#menu');

let popperInstance = null;

area.addEventListener('contextmenu', (e) => {
  e.preventDefault();

  const virtualElement = {
    getBoundingClientRect: () => ({
      width: 0,
      height: 0,
      top: e.clientY,
      bottom: e.clientY,
      left: e.clientX,
      right: e.clientX,
    }),
  };

  if (popperInstance) {
    popperInstance.destroy();
  }

  popperInstance = createPopper(virtualElement, menu, {
    placement: 'right-start',
  });

  menu.style.display = 'block';
});

document.addEventListener('click', () => {
  menu.style.display = 'none';
});

Виртуальные элементы

Popper позволяет позиционировать элементы не только относительно DOM-узлов, но и произвольных координат.

Пример для курсора мыши:

const virtualElement = {
  getBoundingClientRect: () => ({
    width: 0,
    height: 0,
    top: mouseY,
    bottom: mouseY,
    left: mouseX,
    right: mouseX,
  }),
};

Это используется в:

  • контекстных меню
  • drag-and-drop интерфейсах
  • кастомных курсорных подсказках

Типичный сценарий — выпадающий список.

HTML:

<button id="trigger">Открыть</button>
<div id="dropdown">Список</div>

Jav * aScript:

const trigger = document.querySelector('#trigger');
const dropdown = document.querySelector('#dropdown');

const popperInstance = createPopper(trigger, dropdown, {
  placement: 'bottom-start',
});

trigger.addEventListener('click', () => {
  dropdown.classList.toggle('visible');
  popperInstance.update();
});

Добавление закрытия при клике вне:

document.addEventListener('click', (e) => {
  if (!trigger.contains(e.target) && !dropdown.contains(e.target)) {
    dropdown.classList.remove('visible');
  }
});

Закрепление внутри прокручиваемого контейнера

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

createPopper(reference, popper, {
  modifiers: [
    {
      name: 'preventOverflow',
      options: {
        boundary: 'clippingParents',
      },
    },
  ],
});

Popper учитывает:

  • scroll родителя
  • viewport
  • clipping контексты

Интерактивный tooltip (hover + click)

Комбинированное поведение:

let isVisible = false;

function toggle() {
  isVisible = !isVisible;
  tooltip.style.display = isVisible ? 'block' : 'none';
  popperInstance.update();
}

button.addEventListener('click', toggle);

button.addEventListener('mouseenter', () => {
  if (!isVisible) tooltip.style.display = 'block';
});

button.addEventListener('mouseleave', () => {
  if (!isVisible) tooltip.style.display = 'none';
});

Использование стратегии позиционирования

Popper поддерживает две стратегии:

  • absolute (по умолчанию)
  • fixed
createPopper(reference, popper, {
  strategy: 'fixed',
});

Применение fixed:

  • модальные окна
  • фиксированные панели
  • элементы внутри position: fixed

Анимации появления

Popper отвечает только за позиционирование, а анимации реализуются через CSS:

#tooltip {
  opacity: 0;
  transition: opacity 0.2s ease;
}

#tooltip[data-show] {
  opacity: 1;
}

Можно комбинировать с transform:

#tooltip {
  transform: translateY(5px);
}

#tooltip[data-show] {
  transform: translateY(0);
}

Создание собственного модификатора

Пример кастомного модификатора:

const customModifier = {
  name: 'customLogger',
  enabled: true,
  phase: 'main',
  fn({ state }) {
    console.log(state.placement);
  },
};

createPopper(reference, popper, {
  modifiers: [customModifier],
});

Структура модификатора:

  • name — имя
  • enabled — включён ли
  • phase — этап выполнения
  • fn — функция обработки

Работа с несколькими Popper-экземплярами

Создание нескольких tooltip:

const buttons = document.querySelectorAll('.btn');

buttons.forEach((btn) => {
  const tooltip = btn.nextElementSibling;

  createPopper(btn, tooltip, {
    placement: 'top',
  });
});

Уничтожение экземпляра

Важно освобождать ресурсы:

popperInstance.destroy();

Применение:

  • при удалении элемента
  • при смене состояния интерфейса
  • в SPA при размонтировании компонентов

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

Пример с React (логика):

useEffect(() => {
  const popper = createPopper(ref.current, tooltip.current);

  return () => {
    popper.destroy();
  };
}, []);

Пример с Vue:

onMounted(() => {
  instance = createPopper(reference.value, popper.value);
});

onBeforeUnmount(() => {
  instance.destroy();
});

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

Ключевые подходы:

  • избегать частых update() без необходимости
  • использовать eventListeners модификатор

Отключение слушателей:

{
  name: 'eventListeners',
  options: {
    scroll: false,
    resize: false,
  },
}

Ручной контроль:

popperInstance.update();

Комплексный пример: tooltip с автопозиционированием и стрелкой

createPopper(button, tooltip, {
  placement: 'auto',
  modifiers: [
    {
      name: 'offset',
      options: {
        offset: [0, 12],
      },
    },
    {
      name: 'flip',
      options: {
        fallbackPlacements: ['top', 'right', 'left'],
      },
    },
    {
      name: 'arrow',
      options: {
        element: arrow,
      },
    },
    {
      name: 'preventOverflow',
      options: {
        padding: 8,
      },
    },
  ],
});

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

  • автоматический выбор позиции
  • защита от выхода за экран
  • корректная работа стрелки
  • гибкая настройка отступов

Частые ошибки

1. Отсутствие position у popper-элемента

position: absolute;

2. Неправильная структура стрелки

Требуется атрибут:

data-popper-arrow

3. Забытый update()

При динамическом контенте:

popperInstance.update();

4. Конфликт overflow

Родитель с overflow: hidden может обрезать popper.

Решение:

boundary: document.body

Итоговые паттерны применения

  • tooltip — подсказки
  • dropdown — меню
  • popover — информационные блоки
  • context menu — меню по координатам
  • floating UI — любые плавающие элементы

Popper.js выступает универсальным инструментом позиционирования, позволяющим абстрагироваться от сложной геометрии DOM и сосредоточиться на логике интерфейса.