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

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

Ключевые свойства модификатора:

  • name — уникальное имя модификатора.
  • enabled — логическое значение, активен модификатор или нет.
  • phase — стадия жизненного цикла, на которой выполняется модификатор. Доступные фазы: beforeRead, read, afterRead, beforeMain, main, afterMain, beforeWrite, write, afterWrite.
  • fn — основная функция модификатора, получает объект state и options.
  • effect — функция побочного эффекта, вызывается при инициализации и уничтожении поппера.
  • requires — массив имён модификаторов, от которых зависит данный модификатор.
  • requiresIfExists — массив опциональных зависимостей.

Структура объекта state

Внутри функции модификатора fn доступен объект state, содержащий полную информацию о текущем состоянии поппера:

  • state.elements — ссылки на DOM-элементы: reference и popper.
  • state.styles — текущие CSS-стили для применения к элементам.
  • state.attributes — атрибуты для элементов, например aria-expanded.
  • state.modifiersData — данные от всех модификаторов, которые можно использовать и обновлять.
  • state.rects — размеры и позиции элементов.
  • state.placement — текущее расположение поппера.
  • state.options — глобальные опции Popper.js, включая массив модификаторов.
  • state.scrollParents — массив родительских элементов с прокруткой.

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

Простейший модификатор может добавлять смещение поппера по определённой логике:

const offsetModifier = {
  name: 'customOffset',
  enabled: true,
  phase: 'main',
  fn({ state, options }) {
    const offsetX = options.offsetX || 0;
    const offsetY = options.offsetY || 0;

    state.styles.popper.top = `${parseFloat(state.styles.popper.top) + offsetY}px`;
    state.styles.popper.left = `${parseFloat(state.styles.popper.left) + offsetX}px`;
  },
  options: {
    offsetX: 10,
    offsetY: 20
  }
};

const popperInstance = Popper.createPopper(referenceElement, popperElement, {
  modifiers: [offsetModifier]
});

В этом примере модификатор выполняется на фазе main, после вычисления стандартной позиции, добавляя дополнительные смещения по X и Y.

Использование функции effect для побочных действий

Модификатор может создавать побочные эффекты, которые управляют DOM напрямую или подписываются на события:

const borderHighlight = {
  name: 'highlightBorder',
  enabled: true,
  phase: 'write',
  fn({ state }) {
    state.elements.popper.style.border = '2px solid red';
  },
  effect({ state }) {
    const popper = state.elements.popper;
    popper.addEventListener('mouseenter', () => popper.style.borderColor = 'blue');
    return () => {
      popper.removeEventListener('mouseenter', () => popper.style.borderColor = 'blue');
    };
  }
};

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

Взаимодействие модификаторов через modifiersData

Модификаторы могут передавать данные между собой через state.modifiersData:

const calculateDistance = {
  name: 'calculateDistance',
  enabled: true,
  phase: 'read',
  fn({ state }) {
    const rect = state.rects.popper;
    state.modifiersData.distanceFromTop = rect.top;
  }
};

const applyDistanceClass = {
  name: 'applyDistanceClass',
  enabled: true,
  phase: 'write',
  requires: ['calculateDistance'],
  fn({ state }) {
    const distance = state.modifiersData.calculateDistance.distanceFromTop;
    if (distance > 100) {
      state.elements.popper.classList.add('far-from-top');
    } else {
      state.elements.popper.classList.remove('far-from-top');
    }
  }
};

Таким образом, один модификатор вычисляет данные, а другой использует их для управления стилями или классами.

Фазы выполнения модификаторов

Выбор правильной фазы критически важен для корректного поведения:

  • read и beforeRead — сбор информации о DOM без изменений.
  • main и beforeMain — основная логика позиционирования.
  • write и afterWrite — применение стилей и атрибутов в DOM.
  • effect — побочные действия, подписки на события, создание сторонних эффектов.

Неправильный выбор фазы может привести к конфликту с другими модификаторами или некорректной визуализацией поппера.

Динамическое включение и отключение модификаторов

Модификаторы можно активировать и деактивировать во время работы:

popperInstance.setOptions({
  modifiers: [
    { ...offsetModifier, enabled: false },
    { ...borderHighlight, enabled: true }
  ]
});

Это позволяет управлять поведением всплывающих элементов в зависимости от состояния интерфейса.

Итоговая структура кастомного модификатора

Для надёжной работы модификатора рекомендуется соблюдать следующие принципы:

  1. Чётко указывать phase в зависимости от задачи.
  2. Использовать state.modifiersData для передачи данных между модификаторами.
  3. Очищать побочные эффекты через возвращаемую функцию из effect.
  4. Указывать зависимости через requires и requiresIfExists, чтобы избежать конфликтов.
  5. Всегда проверять доступность DOM-элементов перед изменением стилей или атрибутов.

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