Popper.js — это библиотека для управления позиционированием элементов, таких как всплывающие подсказки, тултипы и dropdown-меню, с учётом ограничений окна браузера. Когда речь идёт о нескольких Popper-элементах на одной странице, возникает задача синхронизации их поведения, предотвращения перекрытия и оптимизации производительности.
Каждый Popper создаётся через конструктор
createPopper(reference, popper, options), где:
reference — элемент, к которому привязан Popper.popper — сам всплывающий элемент.options — конфигурация позиции, стратегия рендеринга и
модификаторы.const button1 = document.querySelector('#button1');
const tooltip1 = document.querySelector('#tooltip1');
const popper1 = Popper.createPopper(button1, tooltip1, {
placement: 'top',
modifiers: [{ name: 'offset', options: { offset: [0, 8] } }],
});
const button2 = document.querySelector('#button2');
const tooltip2 = document.querySelector('#tooltip2');
const popper2 = Popper.createPopper(button2, tooltip2, {
placement: 'bottom',
modifiers: [{ name: 'offset', options: { offset: [0, 8] } }],
});
Каждый Popper работает независимо, но при наличии нескольких элементов требуется учитывать потенциальные конфликты в позиции и перекрытие контента.
Для управления несколькими Popper-элементами стоит использовать следующие модификаторы:
Для нескольких Popper важно объединять их модификаторы так, чтобы они не противоречили друг другу.
Если на странице несколько Popper, обновление их можно выполнять централизованно:
const poppers = [popper1, popper2];
function updateAllPoppers() {
poppers.forEach(p => p.update());
}
window.addEventListener('resize', updateAllPoppers);
window.addEventListener('scroll', updateAllPoppers);
Метод update() пересчитывает позиции с учётом
модификаторов. При динамическом изменении DOM это помогает избежать
накладывающихся подсказок.
Раздельные контейнеры Каждый Popper помещается в
отдельный контейнер div, чтобы избежать пересечения с
другими Popper.
Использование модификатора flip с границами
const popper = Popper.createPopper(reference, tooltip, {
placement: 'top',
modifiers: [
{
name: 'flip',
options: {
fallbackPlacements: ['bottom', 'right', 'left'],
boundary: document.body
}
}
]
});
const dynamicOffset = (index) => [0, 8 + index * 40];
poppers.forEach((p, i) => {
p.setOptions({
modifiers: [{ name: 'offset', options: { offset: dynamicOffset(i) } }]
});
});
Иногда необходимо, чтобы Popper-элементы двигались согласованно, например, список всплывающих подсказок. Для этого используют подход «группы»:
const popperGroup = [];
popperGroup.push(popper1, popper2, popper3);
function updateGroup() {
popperGroup.forEach(p => p.update());
}
popperGroup.forEach((p, index) => {
p.setOptions({
modifiers: [{ name: 'offset', options: { offset: [0, index * 10] } }]
});
});
Использование computeStyles с
gpuAcceleration: true позволяет плавно анимировать
несколько Popper без падений производительности:
modifiers: [
{ name: 'computeStyles', options: { gpuAcceleration: true } }
]
Важно учитывать, что каждый Popper генерирует отдельные
transform и top/left, поэтому при
наложении анимации нужно синхронизировать длительность и задержки, чтобы
не было дерганий.
При множественных Popper:
passive: true для событий прокрутки.const buttons = document.querySelectorAll('.button');
const tooltips = document.querySelectorAll('.tooltip');
const poppers = [];
buttons.forEach((btn, index) => {
const popper = Popper.createPopper(btn, tooltips[index], {
placement: 'top',
modifiers: [
{ name: 'offset', options: { offset: [0, index * 10] } },
{ name: 'flip', options: { fallbackPlacements: ['bottom'] } },
{ name: 'preventOverflow', options: { padding: 8 } },
]
});
poppers.push(popper);
});
function updateAll() {
poppers.forEach(p => p.update());
}
window.addEventListener('resize', updateAll);
window.addEventListener('scroll', updateAll);
Такой подход обеспечивает координированное позиционирование и предотвращает перекрытия, одновременно поддерживая плавность анимаций и корректное поведение при скролле и изменении размеров окна.