Чтение и модификация state

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

  • elements — объект с полями reference и popper, содержащий DOM-элементы привязки и поппера.
  • rects — объект с измерениями элементов: reference и popper, где хранятся размеры и координаты.
  • placement — строка текущего расположения поппера относительно референс-элемента (top, bottom-start, right-end и т.д.).
  • strategy — стратегия позиционирования: absolute или fixed.
  • modifiersData — объект, где каждый модификатор может хранить собственные данные между вызовами.
  • orderedModifiers — массив всех модификаторов, отсортированных по их порядку выполнения.
  • options — глобальные опции Popper.js, применённые при инициализации.
  • styles и attributes — хранилища для динамических стилей и атрибутов, которые будут применены к DOM.

Эта структура state позволяет модификаторам как читать текущее положение элементов, так и модифицировать его для достижения нужного поведения.


Чтение state

Для получения информации о текущем положении поппера используются поля rects и placement:

function logPopperPosition({ state }) {
  console.log("Placement:", state.placement);
  console.log("Reference rect:", state.rects.reference);
  console.log("Popper rect:", state.rects.popper);
}
  • state.rects.reference содержит объект с полями x, y, width, height — размеры и координаты референс-элемента.
  • state.rects.popper содержит аналогичные данные для поппера.
  • state.placement позволяет определить текущее направление поппера, которое можно использовать для изменения логики отображения (например, менять стрелку или смещать контент).

Модификация state

Поппер.js предоставляет возможность изменять state через модификаторы. Модификатор — это объект с обязательным полем fn, функцией, которая принимает объект с ключами { state, name, options }.

Пример изменения позиции поппера с помощью модификатора:

const offsetModifier = {
  name: 'customOffset',
  enabled: true,
  phase: 'main',
  fn({ state, options }) {
    const offsetX = options.offsetX || 0;
    const offsetY = options.offsetY || 0;
    state.styles.popper.left = `${parseFloat(state.styles.popper.left || 0) + offsetX}px`;
    state.styles.popper.top = `${parseFloat(state.styles.popper.top || 0) + offsetY}px`;
  },
  options: {
    offsetX: 10,
    offsetY: 20
  }
};
  • state.styles.popper — объект, где хранятся стили, применяемые к попперу.
  • Модификаторы могут использовать modifiersData для сохранения промежуточных вычислений.

Фазы модификаторов

Модификаторы выполняются в определённой последовательности, называемой фазами:

  1. beforeRead — до измерений элементов.
  2. read — чтение размеров и состояния.
  3. afterRead — после чтения.
  4. beforeMain — подготовка перед основной модификацией.
  5. main — основная фаза модификации state.
  6. afterMain — после основной модификации.
  7. beforeWrite — подготовка к записи стилей.
  8. write — применение стилей и атрибутов к DOM.
  9. afterWrite — финальные операции после записи.

Каждая фаза позволяет изменять state в соответствии с контекстом. Основная фаза main используется для вычислений и обновления координат, write — для применения изменений к DOM.


Управление modifiersData

Поле modifiersData позволяет хранить промежуточные данные между вызовами модификаторов:

const storeDataModifier = {
  name: 'storeData',
  enabled: true,
  phase: 'main',
  fn({ state }) {
    state.modifiersData.storeData = {
      previousPlacement: state.placement,
      popperHeight: state.rects.popper.height
    };
  }
};
  • Это особенно полезно для сложных модификаторов, которые зависят от результатов предыдущих вычислений.
  • Использование modifiersData предотвращает повторные расчёты и позволяет динамически адаптировать позиционирование.

Динамическое изменение placement

State позволяет не только читать, но и динамически изменять placement в зависимости от условий:

const flipPlacementModifier = {
  name: 'conditionalFlip',
  enabled: true,
  phase: 'main',
  fn({ state }) {
    if (state.rects.popper.bottom > window.innerHeight) {
      state.placement = 'top';
    }
  }
};
  • После изменения placement, Popper.js пересчитывает координаты в следующем цикле обновления.
  • Такая логика позволяет автоматически подстраивать позицию поппера под видимую область.

Управление стилями и атрибутами

Изменение state.styles и state.attributes напрямую влияет на внешний вид и поведение поппера:

state.styles.popper.transform = 'translate3d(0, 10px, 0)';
state.attributes.popper = { 'data-show': true };
  • state.styles хранит CSS-свойства, которые будут применены при фазе write.
  • state.attributes — объект атрибутов, которые можно добавить к элементу для управления состоянием или для стилизации через селекторы [data-show].

Чтение размеров и позиции с учётом скролла и стратегии

Поле strategy определяет способ позиционирования: absolute или fixed. Оно влияет на то, как координаты в state.rects преобразуются в реальные стили. Для fixed координаты привязаны к viewport, для absolute — к ближайшему offsetParent.

if (state.strategy === 'fixed') {
  // координаты относительно окна браузера
} else {
  // координаты относительно родительского элемента
}

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


Рекомендации по работе с state

  • Всегда работать с копиями данных при сложных вычислениях, чтобы избежать конфликтов между модификаторами.
  • Использовать modifiersData для хранения промежуточных значений.
  • Применять изменения к state.styles и state.attributes, а не напрямую к DOM, чтобы сохранить совместимость с жизненным циклом Popper.js.
  • Чтение rects и placement перед модификацией позволяет принимать решения на основе актуальных размеров и позиции элементов.