Зависимости между модификаторами

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


Основные принципы зависимостей

  1. Модификатор может зависеть от результатов другого модификатора. Например, модификатор flip проверяет, хватает ли места для отображения поппера. Он должен выполняться после модификатора preventOverflow, который ограничивает выход поппера за границы видимой области.

  2. Зависимости задаются через поле requires. Каждому модификатору можно определить массив других модификаторов, которые должны быть выполнены до него:

{
  name: 'flip',
  enabled: true,
  phase: 'main',
  requires: ['preventOverflow']
}

В этом примере flip будет запускаться только после успешного применения preventOverflow.

  1. Фаза выполнения (phase) определяет момент, когда модификатор применяется. Модификаторы выполняются в следующих фазах: beforeRead, read, afterRead, beforeMain, main, afterMain, beforeWrite, write, afterWrite. Если модификатор зависит от другого, важно согласовать их фазы: зависимый модификатор должен находиться в той же фазе или позже.

Типовые зависимости модификаторов

  • offset и preventOverflow Модификатор offset смещает поппер относительно reference-элемента. Если одновременно применяется preventOverflow, важно, чтобы сначала выполнялся offset, а затем preventOverflow, чтобы корректно ограничить поппер внутри границ.

  • flip и preventOverflow Модификатор flip ориентируется на доступное пространство. Если preventOverflow ограничивает поппер раньше, flip будет корректно определять сторону для переворота.

  • arrow и offset Модификатор arrow отвечает за позиционирование стрелки поппера. Чтобы стрелка правильно отображалась, необходимо сначала вычислить смещение поппера через offset.


Определение зависимостей через requiresIfExists

Popper.js позволяет использовать requiresIfExists для необязательных зависимостей:

{
  name: 'arrow',
  enabled: true,
  phase: 'main',
  requiresIfExists: ['offset']
}

Это означает, что если модификатор offset присутствует, он должен выполняться до arrow. Если offset отсутствует, выполнение arrow не будет нарушено. Такой подход полезен для библиотек и компонентов с динамическим набором модификаторов.


Влияние порядка модификаторов

Даже если зависимость явно не указана, порядок модификаторов в массиве имеет значение:

const popperInstance = Popper.createPopper(reference, popper, {
  modifiers: [
    { name: 'offset', options: { offset: [0, 8] } },
    { name: 'preventOverflow' },
    { name: 'flip' }
  ]
});

В этом примере:

  1. offset смещает поппер.
  2. preventOverflow ограничивает положение поппера.
  3. flip при необходимости переворачивает поппер.

Если изменить порядок, например, поместив flip перед preventOverflow, возможны неправильные перевороты, потому что flip не увидит ограничение границ.


Практические рекомендации

  • Использовать requires для обязательных зависимостей, чтобы Popper.js сам корректировал порядок выполнения.
  • Для необязательных зависимостей применять requiresIfExists.
  • Проверять фазу выполнения, чтобы зависимый модификатор не запускался раньше, чем модификатор, от которого он зависит.
  • При добавлении кастомных модификаторов всегда указывать зависимости, если модификатор использует результаты других модификаторов.
  • Для визуально сложных компонентов тестировать комбинации модификаторов с разным порядком и фазами, чтобы убедиться в корректном поведении поппера.

Пример комплексной конфигурации

const popper = Popper.createPopper(reference, popperElement, {
  modifiers: [
    {
      name: 'offset',
      options: { offset: [0, 10] },
      phase: 'main'
    },
    {
      name: 'preventOverflow',
      options: { padding: 8 },
      phase: 'main',
      requires: ['offset']
    },
    {
      name: 'flip',
      options: { fallbackPlacements: ['top', 'right'] },
      phase: 'main',
      requires: ['preventOverflow']
    },
    {
      name: 'arrow',
      options: { element: arrowElement },
      phase: 'main',
      requiresIfExists: ['offset']
    }
  ]
});

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