Условные модификаторы

В архитектуре Popper.js модификаторы (modifiers) представляют собой ключевой механизм расширения поведения позиционирования. Условные модификаторы — это модификаторы, которые применяются только при выполнении определённых условий. Они позволяют динамически управлять логикой позиционирования без изменения основного кода и без создания отдельных экземпляров Popper.

Условность достигается за счёт:

  • включения/отключения модификатора (enabled)
  • проверки состояния в функции модификатора
  • изменения параметров модификатора на лету

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

Каждый модификатор в Popper.js описывается объектом со следующими свойствами:

{
  name: 'exampleModifier',
  enabled: true,
  phase: 'main',
  fn({ state, options, name, instance }) {
    // логика модификатора
  },
  options: {},
  requires: []
}

Условность чаще всего реализуется через:

  • свойство enabled
  • логику внутри fn

Управление через свойство enabled

Самый простой способ создать условный модификатор — управлять его активностью через флаг enabled.

createPopper(reference, popper, {
  modifiers: [
    {
      name: 'flip',
      enabled: window.innerWidth > 768
    }
  ]
});

В данном примере модификатор flip будет работать только на экранах шире 768px.

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

  • значение enabled вычисляется при инициализации
  • изменение условия требует пересоздания Popper или обновления настроек

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

Popper.js позволяет изменять модификаторы после создания экземпляра через метод setOptions.

instance.setOptions(options => ({
  ...options,
  modifiers: options.modifiers.map(mod =>
    mod.name === 'offset'
      ? { ...mod, enabled: shouldEnableOffset() }
      : mod
  )
}));

Такой подход позволяет:

  • реагировать на изменения состояния приложения
  • переключать поведение без уничтожения Popper

Условная логика внутри fn

Более гибкий подход — проверка условий непосредственно в функции модификатора:

{
  name: 'conditionalOffset',
  enabled: true,
  phase: 'main',
  fn({ state }) {
    if (state.rects.reference.width > 200) {
      state.modifiersData.popperOffsets.x += 10;
    }
  }
}

Преимущества:

  • нет необходимости включать/выключать модификатор
  • условие может учитывать текущее состояние (state)

Использование state для условий

Объект state содержит всю информацию о текущем положении:

  • state.rects — размеры reference и popper
  • state.placement — текущее размещение
  • state.modifiersData — данные других модификаторов

Пример:

fn({ state }) {
  if (state.placement.startsWith('top')) {
    state.styles.popper.backgroundColor = 'red';
  }
}

Таким образом можно создавать поведение, зависящее от текущего положения элемента.


Условные зависимости (requires)

Модификаторы могут зависеть от других модификаторов. Это позволяет строить цепочки условий.

{
  name: 'customModifier',
  requires: ['offset'],
  fn({ state }) {
    if (state.modifiersData.offset) {
      // логика при наличии offset
    }
  }
}

Если зависимый модификатор отключён, условие автоматически не выполняется.


Комбинирование условий

Часто требуется учитывать несколько факторов одновременно:

fn({ state }) {
  const isMobile = window.innerWidth < 768;
  const isBottom = state.placement === 'bottom';

  if (isMobile && isBottom) {
    state.modifiersData.popperOffsets.y += 20;
  }
}

Такой подход позволяет:

  • адаптировать поведение под разные устройства
  • учитывать контекст размещения

Условные параметры options

Модификаторы могут менять своё поведение через options, которые также можно формировать динамически:

{
  name: 'offset',
  options: {
    offset: ({ placement }) => {
      if (placement === 'top') {
        return [0, 20];
      }
      return [0, 10];
    }
  }
}

Функция внутри options получает доступ к состоянию и может возвращать разные значения.


Условное изменение фаз выполнения

Каждый модификатор выполняется в определённой фазе (phase):

  • beforeRead
  • read
  • afterRead
  • beforeMain
  • main
  • afterMain
  • beforeWrite
  • write
  • afterWrite

Иногда требуется выполнять логику только при определённых условиях, связанных с фазой:

fn({ state }) {
  if (state.elements.reference instanceof HTMLElement) {
    // выполнять только для DOM-элементов
  }
}

Практический пример: адаптивный tooltip

createPopper(reference, tooltip, {
  modifiers: [
    {
      name: 'offset',
      options: {
        offset: ({ placement }) => {
          return placement === 'top' ? [0, 16] : [0, 8];
        }
      }
    },
    {
      name: 'adaptiveBehavior',
      enabled: true,
      phase: 'main',
      fn({ state }) {
        if (window.innerWidth < 600) {
          state.placement = 'bottom';
        }
      }
    }
  ]
});

Здесь:

  • смещение зависит от положения
  • на мобильных устройствах принудительно меняется placement

Использование пользовательских флагов

Условные модификаторы могут опираться на пользовательские параметры:

const isDebugMode = true;

{
  name: 'debugModifier',
  enabled: isDebugMode,
  fn({ state }) {
    console.log(state);
  }
}

Это удобно для:

  • отладки
  • включения экспериментальных функций

Производительность и условные модификаторы

Чрезмерное использование условий внутри fn может повлиять на производительность, особенно при частых обновлениях (scroll, resize).

Рекомендуется:

  • по возможности использовать enabled
  • минимизировать сложные вычисления внутри fn
  • кешировать результаты условий

Распространённые ошибки

1. Изменение enabled без обновления Popper

modifier.enabled = false; // не сработает

Требуется:

instance.setOptions(...)

2. Игнорирование фаз выполнения

Некоторые данные доступны только на определённых этапах. Проверки должны учитывать фазу.


3. Конфликт модификаторов

Несколько условных модификаторов могут изменять одни и те же данные:

state.modifiersData.popperOffsets

Это может приводить к непредсказуемым результатам.


Паттерны проектирования

1. Feature Toggle

enabled: features.enableNewPositioning

2. Context-aware модификаторы

if (state.elements.reference.dataset.type === 'menu')

3. Responsive модификаторы

if (window.matchMedia('(max-width: 768px)').matches)

Итоговые принципы

  • Условность должна быть прозрачной и предсказуемой
  • Простые условия — через enabled
  • Сложные — внутри fn
  • Изменяемость — через setOptions
  • Логика должна учитывать текущее состояние state

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