Ленивая инициализация

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

Принцип работы

Floating UI разделяет логику позиционирования и рендеринга. Ленивая инициализация предполагает, что объект floating создаётся и активируется только при событии пользователя — например, при наведении, клике или фокусе. Это позволяет избежать постоянного слежения за размерами и позициями элементов в DOM, снижая нагрузку на браузер.

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

let floatingInstance;

function showTooltip(reference, floating) {
  if (!floatingInstance) {
    floatingInstance = computePosition(reference, floating, {
      middleware: [offset(8), flip(), shift({ padding: 5 })],
    }).then(({ x, y }) => {
      Object.assign(floating.style, {
        left: `${x}px`,
        top: `${y}px`,
      });
    });
  }
  floating.style.display = 'block';
}

В этом примере вычисление позиции происходит только при первом показе тултипа. Последующие вызовы используют уже инициализированный объект.

Преимущества ленивой инициализации

  1. Снижение нагрузки на страницу Без ленивой инициализации все попапы на странице могли бы постоянно пересчитывать позиции при изменении размера окна, скролле или обновлении DOM. Ленивая инициализация делает процесс вычислений «по требованию».

  2. Уменьшение количества слушателей событий Floating UI использует обработчики для слежения за размерами и скроллом. Создание этих слушателей только при первом открытии элемента снижает вероятность утечек памяти.

  3. Ускорение рендеринга начальной страницы При большом количестве интерактивных элементов на странице, их мгновенное позиционирование может замедлить загрузку. Ленивая инициализация откладывает вычисления, что ускоряет первоначальный рендер.

Комбинирование с autoUpdate

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

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

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

  return cleanup; // Позволяет отключить слежение при закрытии
}

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

Интеграция с пользовательскими событиями

Чаще всего ленивую инициализацию применяют для следующих сценариев:

  • Наведение мыши: позиция вычисляется только при mouseenter.
  • Клик по кнопке: элемент появляется и позиционируется при событии click.
  • Фокус на поле ввода: тултипы или подсказки появляются при focus, а удаляются при blur.
const button = document.querySelector('#tooltip-button');
const tooltip = document.querySelector('#tooltip');

button.addEventListener('mouseenter', () => showTooltip(button, tooltip));
button.addEventListener('mouseleave', () => tooltip.style.display = 'none');

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

Использование с React и Vue

В React и Vue ленивую инициализацию удобно реализовать через хуки или реактивные свойства, вызывая computePosition только при рендере активного компонента:

// React пример
import { useState, useRef } from 'react';
import { computePosition, offset, flip } from '@floating-ui/dom';

function Tooltip({ children }) {
  const [visible, setVisible] = useState(false);
  const referenceRef = useRef(null);
  const floatingRef = useRef(null);

  const show = () => {
    if (referenceRef.current && floatingRef.current) {
      computePosition(referenceRef.current, floatingRef.current, {
        middleware: [offset(8), flip()],
      }).then(({ x, y }) => {
        Object.assign(floatingRef.current.style, { left: `${x}px`, top: `${y}px` });
        setVisible(true);
      });
    }
  };

  return (
    <div>
      <button ref={referenceRef} onMouseEn ter={show} onMouseLe ave={() => setVisible(false)}>
        Hover me
      </button>
      {visible && <div ref={floatingRef} className="tooltip">{children}</div>}
    </div>
  );
}

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

Практические советы

  • Инициализация по первому взаимодействию: избегать создания Floating UI при загрузке страницы, если элемент может не использоваться.
  • Отмена автообновления после закрытия: всегда хранить ссылку на функцию очистки autoUpdate и вызывать её при скрытии элемента.
  • Использование промисов: computePosition возвращает промис, поэтому позиция может быть вычислена асинхронно, что удобно для динамических интерфейсов.

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