Наблюдение за scroll и resize

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


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

Floating UI использует контексты прокрутки (scroll parents) и наблюдатели размеров (resize observers) для определения актуальной позиции элемента относительно цели. Главные концепции:

  1. Контексты прокрутки – это все элементы DOM, которые могут повлиять на положение целевого элемента. Библиотека отслеживает их изменения через scroll события.
  2. ResizeObserver – современный API, который позволяет обнаруживать изменения размеров элементов или окна без необходимости подписки на window.resize.

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


Автообновление позиции через autoUpdate

Функция autoUpdate позволяет автоматически следить за scroll и resize событий, а также за изменениями DOM:

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

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

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

Ключевые моменты работы autoUpdate:

  • Следит за scroll всех предков элемента.
  • Обрабатывает resize целевого элемента и окна.
  • Использует MutationObserver, чтобы отслеживать изменения DOM, которые могут повлиять на положение.
  • Возвращает функцию cleanup, которую необходимо вызвать при удалении элементов, чтобы убрать подписки и не допустить утечки памяти.

Настройка отслеживания scroll

Для оптимизации производительности можно указать конкретные scroll parents, чтобы не реагировать на каждый скролл документа:

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

const scrollParents = [document.body, document.querySelector('.container')];

const cleanup = autoUpdate(reference, floating, updatePosition, {
  ancestorScroll: scrollParents,
});

function updatePosition() {
  computePosition(reference, floating).then(({ x, y }) => {
    Object.assign(floating.style, { left: `${x}px`, top: `${y}px` });
  });
}

Такой подход особенно полезен для сложных интерфейсов с большим количеством вложенных скроллов, где автоматическая подписка на все элементы может быть избыточной.


Оптимизация производительности

При постоянной прокрутке и изменении размеров важно минимизировать лишние вызовы computePosition. Floating UI предлагает несколько механизмов:

  1. Throttling и Debouncing – встроенные методы для контроля частоты обновлений.
  2. Фильтрация изменений – обновление позиции только если элемент видим на экране.
  3. Использование requestAnimationFrame – обеспечивает синхронизацию с перерисовкой браузера, снижая нагрузку на главный поток.

Пример с requestAnimationFrame:

let frame;

function updatePosition() {
  if (frame) cancelAnimationFrame(frame);
  frame = requestAnimationFrame(() => {
    computePosition(reference, floating).then(({ x, y }) => {
      Object.assign(floating.style, { left: `${x}px`, top: `${y}px` });
    });
  });
}

Использование ResizeObserver напрямую

Иногда требуется отслеживать изменения размеров не только floating элемента, но и reference. Floating UI позволяет это через ResizeObserver:

const ro = new ResizeObserver(() => updatePosition());
ro.observe(reference);
ro.observe(floating);

// Для очистки подписки
ro.disconnect();

Преимущество – моментальное реагирование на изменение размеров без необходимости слушать глобальное событие resize.


Интеграция с виртуальными элементами

Floating UI поддерживает виртуальные reference элементы, например, для canvas или custom рендеров. В таких случаях scroll и resize события отслеживаются вручную через autoUpdate с передачей кастомной логики:

const virtualReference = {
  getBoundingClientRect: () => ({
    x: 100,
    y: 50,
    width: 200,
    height: 50,
    top: 50,
    left: 100,
    right: 300,
    bottom: 100,
  }),
};

autoUpdate(virtualReference, floating, updatePosition);

Best Practices

  • Всегда вызывать cleanup при удалении элементов, чтобы предотвратить утечки памяти.
  • Фильтровать события scroll и resize только для критичных элементов интерфейса.
  • Использовать requestAnimationFrame для синхронизации обновлений с рендером.
  • Комбинировать с middleware Floating UI для дополнительной логики позиционирования (offset, flip, shift), чтобы поведение элементов оставалось корректным при изменении размеров и прокрутке.

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