Отслеживание изменений DOM

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


autoUpdate — автоматическое обновление позиции

Метод autoUpdate позволяет Floating UI автоматически отслеживать изменения размера или положения элементов. Он принимает четыре аргумента:

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

const cleanup = autoUpdate(referenceElement, floatingElement, () => {
  computePosition(referenceElement, floatingElement, {
    placement: 'bottom',
  }).then(({ x, y }) => {
    Object.assign(floatingElement.style, {
      left: `${x}px`,
      top: `${y}px`,
    });
  });
});

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

  • referenceElement — элемент, относительно которого позиционируется плавающий элемент.
  • floatingElement — сам плавающий элемент.
  • Третьим аргументом передается функция обновления позиции, вызываемая автоматически при изменениях.
  • Возвращаемое значение cleanup — функция для прекращения наблюдения.

autoUpdate объединяет три типа наблюдения:

  1. ResizeObserver — отслеживает изменение размеров reference или floating элемента.
  2. MutationObserver — отслеживает изменение DOM (добавление/удаление элементов, атрибутов).
  3. scroll/resize listeners — отслеживает прокрутку и изменение размеров окна.

Это делает autoUpdate универсальным решением для реактивного позиционирования.


Настройка MutationObserver для специфических изменений

Иногда требуется отслеживать только определенные изменения DOM, чтобы избежать лишних вычислений позиции. Floating UI позволяет интегрировать кастомные наблюдатели:

const observer = new MutationObserver(() => {
  computePosition(referenceElement, floatingElement, {
    placement: 'top',
  }).then(({ x, y }) => {
    Object.assign(floatingElement.style, { left: `${x}px`, top: `${y}px` });
  });
});

observer.observe(referenceElement, {
  attributes: true, // следить за изменением атрибутов
  childList: true,  // следить за добавлением/удалением дочерних узлов
  subtree: true,    // включить всех потомков
});

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

  • Можно наблюдать атрибуты, дочерние элементы и субдерево.
  • Позволяет оптимизировать производительность, вызывая computePosition только при нужных изменениях.
  • observer.disconnect() останавливает наблюдение.

Использование referenceHidden и middleware для динамических изменений

Floating UI предоставляет middleware, которые реагируют на изменение DOM и видимости:

  • hide — автоматически определяет, когда reference скрыт (например, display: none или вынос за пределы viewport) и можно скрывать плавающий элемент.
  • flip и shift — корректируют позицию при изменении размеров или положения reference.

Пример с middleware:

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

computePosition(referenceElement, floatingElement, {
  placement: 'bottom',
  middleware: [offset(8), flip(), shift(), hide()],
}).then(({ x, y, middlewareData }) => {
  Object.assign(floatingElement.style, { left: `${x}px`, top: `${y}px` });

  if (middlewareData.hide?.referenceHidden) {
    floatingElement.style.display = 'none';
  } else {
    floatingElement.style.display = 'block';
  }
});

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

  • middlewareData.hide.referenceHidden сообщает, что reference не виден.
  • Комбинирование flip и shift обеспечивает адаптивное позиционирование при изменениях DOM или размеров контейнеров.

Оптимизация обновлений при больших DOM

При больших и сложных деревьях DOM важно минимизировать количество вызовов computePosition:

  • Использовать debounce/throttle для MutationObserver или autoUpdate:
let frame;
autoUpdate(referenceElement, floatingElement, () => {
  if (frame) cancelAnimationFrame(frame);
  frame = requestAnimationFrame(() => {
    computePosition(referenceElement, floatingElement, { placement: 'top' })
      .then(({ x, y }) => {
        Object.assign(floatingElement.style, { left: `${x}px`, top: `${y}px` });
      });
  });
});
  • Ограничивать наблюдение только нужными узлами и атрибутами.
  • Использовать позиционирование по transform вместо top/left для лучшей производительности при анимациях.

Практические сценарии отслеживания DOM

  1. Выпадающие меню и тултипы, которые могут менять размеры при динамическом контенте (например, загрузка данных через fetch).
  2. Модальные окна и поповеры, которые меняют позицию при скролле или изменении контейнера.
  3. Редактируемые интерфейсы, где пользователь добавляет/удаляет элементы в DOM, влияя на положение reference.

Использование комбинации autoUpdate, MutationObserver и middleware обеспечивает:

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

Это делает Floating UI надежным инструментом для реактивного позиционирования в сложных веб-приложениях.