Lifecycle и обновления позиции

В библиотеке Floating UI жизненный цикл элементов управляется через создание, обновление и уничтожение плавающих компонентов. Основной объект, через который происходит управление позиционированием, — это floating instance, создаваемый функцией computePosition или через хуки, предоставляемые библиотекой (useFloating в React, createFloating в vanilla JS).

Жизненный цикл включает три ключевых этапа:

  1. Инициализация — вычисление начальной позиции элемента относительно reference-элемента.
  2. Обновления — динамическое пересчитывание позиции при изменении размеров, прокрутке или контента.
  3. Разрушение — очистка всех слушателей событий и отмена активных эффектов.

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


Инициализация и computePosition

Функция computePosition(reference, floating, options) возвращает объект с координатами x и y, которые затем можно применить к стилям элемента.

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

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

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

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

  • Reference — элемент, относительно которого позиционируется floating.
  • Floating — сам всплывающий элемент.
  • Middleware — цепочка функций, изменяющих финальную позицию (offset, flip, shift, autoPlacement и др.).

Инициализация — это момент, когда определяется начальная позиция, но она статична, если не подключить механизмы обновления.


Обновления позиции

Floating UI предоставляет два способа поддерживать актуальное позиционирование: автоматическое обновление через autoUpdate и ручное обновление через повторный вызов computePosition.

AutoUpdate

Функция autoUpdate(reference, floating, updateCallback) устанавливает слушатели событий, которые отслеживают:

  • Изменения размера reference или floating (ResizeObserver).
  • Прокрутку родительских контейнеров (scroll).
  • Изменение контента внутри элементов (MutationObserver).
import { autoUpdate, computePosition } from '@floating-ui/dom';

const cleanup = autoUpdate(reference, floating, () => {
  computePosition(reference, floating, { middleware: [offset(8), flip()] }).then(({ x, y }) => {
    Object.assign(floating.style, { left: `${x}px`, top: `${y}px` });
  });
});

// Для остановки обновлений
cleanup();

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

  • autoUpdate возвращает функцию очистки, которая снимает все слушатели.
  • Можно ограничить обновления только определёнными типами событий (например, только scroll).

Ручное обновление

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

button.addEventListener('click', () => {
  computePosition(reference, floating, { middleware: [offset(10)] }).then(({ x, y }) => {
    Object.assign(floating.style, { left: `${x}px`, top: `${y}px` });
  });
});

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


Middleware и влияние на жизненный цикл

Middleware напрямую влияют на жизненный цикл и динамическое обновление:

  • offset — смещает элемент на заданное расстояние.
  • flip — меняет сторону расположения при нехватке места.
  • shift — корректирует позицию, чтобы элемент не выходил за пределы контейнера.
  • autoPlacement — выбирает оптимальную сторону на основе доступного пространства.

Каждый middleware вызывается при каждом обновлении позиции. Важно учитывать порядок: offset всегда применяется после flip, чтобы смещение корректно учитывалось после выбора стороны.


Оптимизация обновлений

Чрезмерное использование autoUpdate может приводить к потере производительности. Рекомендуется:

  • Использовать autoUpdate только для критических элементов, требующих постоянной синхронизации с reference.
  • Ограничивать middleware, вызываемые на каждом обновлении.
  • При сложных интерфейсах — кэшировать позиции и использовать throttle/debounce для callback’ов.

Разрушение и очистка

После завершения жизненного цикла элемента необходимо:

  1. Вызвать функцию очистки autoUpdate для снятия всех слушателей.
  2. Удалить floating элемент из DOM, если он больше не нужен.
  3. Сбросить состояние middleware, если оно сохраняло внутренние данные.
const cleanup = autoUpdate(reference, floating, updatePosition);
// В нужный момент
cleanup();
floating.remove();

Правильная очистка предотвращает утечки памяти и ошибки в динамическом интерфейсе, особенно если плавающие элементы создаются и удаляются часто.


Интеграция с фреймворками

В React или Vue Floating UI можно интегрировать через хуки (useFloating) или компоненты-обёртки. Хуки автоматически управляют жизненным циклом:

  • Подключают autoUpdate при монтировании.
  • Вызывают computePosition при изменении props.
  • Автоматически очищают слушатели при размонтировании компонента.
const { x, y, reference, floating } = useFloating({
  middleware: [offset(10), flip()],
});

return (
  <>
    <button ref={reference}>Open</button>
    <div ref={floating} style={{ position: 'absolute', left: x ?? 0, top: y ?? 0 }}>
      Tooltip
    </div>
  </>
);

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


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