Базовый сценарий использования 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.
Добавляет отступ между элементами:
createPopper(button, tooltip, {
modifiers: [
{
name: 'offset',
options: {
offset: [0, 10],
},
},
],
});
Меняет сторону при нехватке места:
{
name: 'flip',
options: {
fallbackPlacements: ['bottom', 'right'],
},
}
Предотвращает выход за границы контейнера:
{
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,
}),
};
Это используется в:
Типичный сценарий — выпадающий список.
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 учитывает:
Комбинированное поведение:
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 (по умолчанию)fixedcreatePopper(reference, popper, {
strategy: 'fixed',
});
Применение fixed:
position: fixedPopper отвечает только за позиционирование, а анимации реализуются через 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 — функция обработкиСоздание нескольких tooltip:
const buttons = document.querySelectorAll('.btn');
buttons.forEach((btn) => {
const tooltip = btn.nextElementSibling;
createPopper(btn, tooltip, {
placement: 'top',
});
});
Важно освобождать ресурсы:
popperInstance.destroy();
Применение:
Пример с 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();
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
Popper.js выступает универсальным инструментом позиционирования, позволяющим абстрагироваться от сложной геометрии DOM и сосредоточиться на логике интерфейса.