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

В библиотеке Floating UI объект middleware представляет собой фундаментальный инструмент для управления позиционированием всплывающих элементов. Middleware отвечает за модификацию координат, обработку смещений, ограничение области видимости и динамическую адаптацию поведения элементов интерфейса.


Основные принципы работы middleware

middleware всегда передается в функцию computePosition в виде массива объектов или функций. Каждый middleware выполняется последовательно, получая текущие координаты x и y, а также дополнительную информацию о положении элемента и его окружении. На выходе middleware возвращает объект с возможными изменениями этих координат или другими параметрами, влияющими на рендеринг.

Простейший пример структуры middleware:

const middleware = [
  offset(10),
  flip(),
  shift()
];

Здесь offset, flip и shift — встроенные middleware, каждый из которых реализует определенное поведение.


Общая структура объекта middleware

Каждый middleware в Floating UI представляет собой объект или функцию, которая может содержать следующие свойства:

  1. name (строка) Уникальное имя middleware. Оно используется для идентификации в цепочке обработки и для логирования.

    { name: 'offset' }
  2. options (объект, необязательный) Настройки middleware, задающие его поведение. Например, величина смещения, направление срабатывания, ограничения по экрану.

    { name: 'offset', options: { mainAxis: 10, crossAxis: 5 } }
  3. fn (функция) Основная функция middleware, принимающая объект с текущим состоянием позиционирования и возвращающая изменения. Аргументы функции обычно включают:

    • x и y — текущие координаты элемента.
    • placement — позиция относительно reference-элемента (top, bottom, left, right).
    • strategy — стратегия позиционирования (absolute или fixed).
    • elements — объект с элементами reference и floating.
    • rects — размеры reference и floating элементов.
    • platform — объект платформы с низкоуровневыми методами для работы с DOM.

    Функция возвращает объект с изменениями:

    {
      x: newX,
      y: newY,
      data: { customData }
    }

    Пример простого middleware для смещения:

    function customOffset({ x, y, options }) {
      return {
        x: x + (options?.x || 0),
        y: y + (options?.y || 0)
      };
    }
    
    const middleware = { name: 'customOffset', fn: customOffset, options: { x: 20, y: 10 } };

Важные свойства возвращаемого объекта

Middleware может возвращать несколько ключевых полей, влияющих на позиционирование:

  • x / y — измененные координаты, которые будут использоваться для позиционирования элемента.
  • data — произвольные данные, которые могут быть использованы последующими middleware или для логики рендеринга.
  • reset — специальная инструкция, которая сигнализирует о необходимости повторного расчета позиции, часто с новой стратегией или измененными параметрами.

Пример с reset:

function limitToViewport({ x, y, rects, elements }) {
  if (x + rects.floating.width > window.innerWidth) {
    return { reset: { x: window.innerWidth - rects.floating.width } };
  }
  return { x, y };
}

Порядок выполнения и взаимодействие middleware

Middleware обрабатываются последовательно. Каждый следующий middleware получает координаты, модифицированные предыдущими. Это позволяет строить сложные цепочки трансформаций:

  1. offset — добавляет смещение.
  2. flip — меняет позицию при выходе за границы viewport.
  3. shift — подгоняет элемент внутрь видимой области.

Если один из middleware возвращает reset, цепочка прерывается, и происходит повторный вызов computePosition с обновленными параметрами.


Рекомендации по созданию собственного middleware

  1. Всегда использовать name для идентификации middleware.
  2. Использовать data для передачи промежуточных вычислений другим middleware.
  3. Обрабатывать все возможные крайние случаи: выход за границы экрана, перекрытие элементов, изменение размеров reference.
  4. Стараться не изменять elements или rects напрямую, использовать только возвращаемые координаты.

Пример комплексного middleware

function adaptiveShift({ x, y, rects, placement, options }) {
  let newX = x;
  let newY = y;

  if (x + rects.floating.width > window.innerWidth) {
    newX = window.innerWidth - rects.floating.width - (options?.padding || 8);
  }
  if (y + rects.floating.height > window.innerHeight) {
    newY = window.innerHeight - rects.floating.height - (options?.padding || 8);
  }

  return {
    x: newX,
    y: newY,
    data: { adjusted: true }
  };
}

const middleware = [
  offset({ mainAxis: 10 }),
  adaptiveShift({ padding: 12 }),
  flip()
];

В этом примере middleware adaptiveShift динамически корректирует положение, учитывая границы viewport, а цепочка позволяет гибко сочетать смещение, адаптивную подгонку и поворот элемента.


Структура объекта middleware в Floating UI обеспечивает мощный, модульный и предсказуемый механизм управления позиционированием. Понимание ключевых полей name, fn, options и возвращаемых значений x, y, data и reset позволяет создавать как простые, так и сложные решения для любых UI-компонентов.