Переход на Popper.js (в актуальной версии — @popperjs/core) в существующем проекте требует аккуратного подхода, особенно если ранее использовались самописные решения позиционирования или устаревшие библиотеки. Резкая замена часто приводит к регрессиям в интерфейсе: всплывающие подсказки, выпадающие меню и контекстные окна начинают вести себя нестабильно.
Постепенная миграция строится на следующих принципах:
Перед началом миграции необходимо выявить все места, где используется позиционирование элементов:
Типичные источники:
element.getBoundingClientRect()
window.scrollX / scrollY
position: absolute / fixed
Важно определить:
Чтобы не переписывать весь код сразу, вводится промежуточный слой — адаптер. Он инкапсулирует работу с Popper.js и предоставляет API, похожий на существующий.
Пример:
import { createPopper } from '@popperjs/core';
export function createPositioning(reference, popper, options = {}) {
return createPopper(reference, popper, {
placement: options.placement || 'bottom',
modifiers: [
{
name: 'offset',
options: {
offset: options.offset || [0, 8],
},
},
{
name: 'preventOverflow',
options: {
boundary: options.boundary || 'viewport',
},
},
],
});
}
Это позволяет:
На первом этапе новые компоненты используют Popper.js, а старые продолжают работать как прежде.
Стратегия:
Пример:
if (useNewPopper) {
createPositioning(button, tooltip);
} else {
legacyPositioning(button, tooltip);
}
Флаг useNewPopper может быть:
Переход выполняется по категориям UI-элементов:
Самый простой случай:
Пример:
createPopper(referenceEl, tooltipEl, {
placement: 'top',
});
Учитываются:
Добавляются модификаторы:
modifiers: [
{
name: 'flip',
options: {
fallbackPlacements: ['top', 'right'],
},
},
]
Более сложная логика:
Используется обновление позиции:
instance.update();
Popper.js требует явного контроля создания и уничтожения экземпляров.
Создание:
const instance = createPopper(reference, popper);
Уничтожение:
instance.destroy();
При миграции важно:
Модификаторы — ключевая часть Popper.js. При миграции необходимо сопоставить старую логику с новыми возможностями.
Часто используемые:
Пример:
modifiers: [
{
name: 'arrow',
options: {
element: arrowElement,
},
},
]
Если в старом коде была кастомная логика, её можно перенести в собственный модификатор:
const customModifier = {
name: 'customLogic',
enabled: true,
phase: 'main',
fn({ state }) {
// кастомная обработка
},
};
При миграции выявляются проблемные ситуации:
Popper.js решает это через:
strategy: 'fixed' | 'absolute'boundaryeventListenersПример:
createPopper(reference, popper, {
strategy: 'fixed',
});
В проектах с React, Vue или Angular миграция требует обёрток.
Пример для React:
useEffect(() => {
const instance = createPopper(ref.current, popper.current);
return () => {
instance.destroy();
};
}, []);
Важно:
После каждого этапа миграции проводится проверка:
Типы тестов:
После завершения миграции:
Важно убедиться, что:
После полного перехода открываются дополнительные возможности:
Можно:
Пример:
modifiers: [
{
name: 'eventListeners',
options: {
scroll: false,
resize: true,
},
},
]
Грамотная стратегия постепенной миграции позволяет избежать критических сбоев и обеспечивает контролируемый переход на современную систему позиционирования.