Кастомная логика позиционирования

Библиотека Floating UI предоставляет мощный набор инструментов для управления позиционированием плавающих элементов в браузере, таких как тултипы, всплывающие окна и контекстные меню. Важной особенностью является возможность создавать кастомные стратегии позиционирования, выходящие за рамки стандартных алгоритмов (flip, shift, offset), что особенно полезно для сложных интерфейсов и нестандартных макетов.


Функция computePosition

computePosition — центральная функция Floating UI, которая вычисляет координаты плавающего элемента относительно якоря. Она принимает два обязательных параметра: reference (элемент, к которому привязан флоатинг) и floating (сам флоатинг). Также поддерживается объект с опциями:

import { computePosition, offset, flip, shift } from '@floating-ui/dom';

const reference = document.querySelector('#button');
const floating = document.querySelector('#tooltip');

computePosition(reference, floating, {
  placement: 'top',
  middleware: [offset(10), flip(), shift({ padding: 5 })],
}).then(({ x, y }) => {
  Object.assign(floating.style, {
    left: `${x}px`,
    top: `${y}px`,
  });
});

Ключевые моменты:

  • placement задаёт базовое направление (top, bottom, left, right) с опциональными вариациями (-start, -end).
  • middleware позволяет добавлять цепочку функций для изменения поведения позиционирования.

Создание кастомного middleware

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

function restrictToViewport() {
  return {
    name: 'restrictToViewport',
    fn({ x, y, rects, placement }) {
      const viewportWidth = window.innerWidth;
      const viewportHeight = window.innerHeight;
      let newX = Math.min(Math.max(0, x), viewportWidth - rects.floating.width);
      let newY = Math.min(Math.max(0, y), viewportHeight - rects.floating.height);
      return { x: newX, y: newY };
    },
  };
}

computePosition(reference, floating, {
  placement: 'bottom',
  middleware: [restrictToViewport()],
});

Такой подход позволяет полностью контролировать позицию элемента, независимо от стандартных правил.


Динамическая смена позиции

Кастомная логика часто требует смены позиции в зависимости от условий страницы:

function dynamicPlacement() {
  return {
    name: 'dynamicPlacement',
    fn({ placement, rects }) {
      if (rects.reference.top < 100) return { data: { placement: 'bottom' } };
      return { data: { placement } };
    },
  };
}

computePosition(reference, floating, {
  placement: 'top',
  middleware: [dynamicPlacement(), offset(8)],
});

Middleware может возвращать не только новые координаты, но и данные, которые могут использоваться другими middleware для сложной логики.


Работа с нестандартными системами координат

Floating UI поддерживает любые системы координат, включая скроллируемые контейнеры и трансформированные элементы. Для этого используется strategy:

  • absolute — позиционирование относительно документа, учитывая скролл.
  • fixed — позиционирование относительно окна, игнорируя скролл.
computePosition(reference, floating, {
  placement: 'right',
  strategy: 'fixed',
});

Это особенно важно для модальных окон, которые должны оставаться на месте при прокрутке родительских элементов.


Комбинация кастомной логики с встроенными middleware

Можно комбинировать стандартные middleware и свои функции. Например, создать флоатинг, который подсказывает направление пользователя и ограничивается viewport:

computePosition(reference, floating, {
  placement: 'top',
  middleware: [
    offset(5),
    flip(),
    shift({ padding: 10 }),
    restrictToViewport(),
    dynamicPlacement(),
  ],
});

Порядок middleware имеет значение: каждая последующая функция получает координаты после обработки предыдущих.


Ключевые принципы при создании кастомной логики

  1. Минимизировать пересчёты — expensive вычисления в middleware могут замедлять интерфейс.
  2. Передавать данные между middleware через data объект для сложных цепочек.
  3. Использовать strategy осознанно, особенно для фиксированных элементов и скроллируемых контейнеров.
  4. Тестировать на разных viewport и элементах, так как поведение может различаться в зависимости от размеров и прокрутки.

Практическая структура middleware

Middleware обычно имеет следующий формат:

{
  name: 'название',
  options: { ... }, // при необходимости
  async fn({ x, y, placement, rects, middlewareData }) {
    // вычисления
    return { x: newX, y: newY, data: { ... } };
  }
}
  • rects — объект с размерами reference и floating.
  • middlewareData — данные от предыдущих middleware.
  • Возвращаемый объект может содержать x, y, data, или reset для пересчёта с новым placement.

Заключение по подходу

Кастомная логика позиционирования в Floating UI превращает библиотеку из простого тултипа в полноценный инструмент для создания адаптивных и сложных интерфейсов. Гибкость middleware и поддержка различных стратегий позволяют решать практически любые задачи визуального позиционирования на страницах любого уровня сложности.