Ограничения виртуальных элементов

Popper.js предоставляет возможность использовать виртуальные элементы (virtual elements) вместо реальных DOM-узлов в качестве источников для всплывающих подсказок, тултипов или других попперов. Виртуальный элемент — это объект с интерфейсом, совместимым с getBoundingClientRect(), что позволяет библиотеке вычислять позицию поппера без привязки к существующему DOM-элементу.

const virtualElement = {
  getBoundingClientRect: () => ({
    width: 0,
    height: 0,
    top: 100,
    left: 200,
    right: 200,
    bottom: 100,
  }),
};

Ключевой момент: Popper.js использует только координаты, возвращаемые getBoundingClientRect, для вычисления позиции, поэтому свойства элемента, такие как offsetWidth или offsetHeight, не влияют на расположение поппера.


Создание поппера с виртуальным элементом

Для создания поппера с виртуальным элементом используется стандартная функция createPopper, где в качестве первого аргумента передаётся объект, реализующий метод getBoundingClientRect.

import { createPopper } from '@popperjs/core';

const popperInstance = createPopper(virtualElement, tooltipElement, {
  placement: 'top',
});

В этом случае Popper.js будет позиционировать tooltipElement относительно координат виртуального элемента, не завися от DOM-структуры страницы.

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


Ограничения виртуальных элементов

  1. Отсутствие размеров Виртуальные элементы часто используют width: 0 и height: 0, что ограничивает возможности для автоматического определения размера поппера. Если требуется, чтобы поппер учитывал размеры источника, их необходимо задавать явно в методе getBoundingClientRect.

  2. Нет родительской иерархии DOM Виртуальный элемент не находится в DOM, поэтому моды preventOverflow и flip могут работать иначе, если источник не взаимодействует с реальными границами контейнеров.

  3. Ограниченная поддержка событий Поскольку виртуальный элемент не может получать события, все интерактивные действия должны быть привязаны к другим элементам. Например, всплывающая подсказка на выделенном тексте может потребовать слушателей на mouseup и mousemove, чтобы обновлять координаты виртуального элемента.

  4. Требуется ручное обновление Для виртуальных элементов Popper.js не отслеживает изменения положения или размера автоматически. Любые обновления должны инициироваться вручную через метод update:

popperInstance.update();
  1. Отсутствие стилей и визуальных ограничений Поскольку виртуальные элементы не имеют реального рендера, свойства CSS, такие как margin, padding, transform, не влияют на позиционирование поппера. Всё рассчитывается исключительно на основе возвращаемого объекта getBoundingClientRect.

Примеры использования ограничений

Поппер для текстового выделения:

function getSelectionRect() {
  const selection = window.getSelection();
  if (!selection.rangeCount) return null;
  const range = selection.getRangeAt(0);
  return range.getBoundingClientRect();
}

const virtualElement = {
  getBoundingClientRect: () => getSelectionRect() || { top: 0, left: 0, bottom: 0, right: 0, width: 0, height: 0 },
};

const popperInstance = createPopper(virtualElement, tooltipElement, {
  placement: 'top',
});

Здесь координаты виртуального элемента изменяются динамически, но необходимо вручную вызывать popperInstance.update() при каждом изменении выделения, иначе поппер останется на старой позиции.


Влияние модификаторов на виртуальные элементы

  • flip: Поппер будет пытаться изменить сторону отображения, но из-за отсутствия DOM-контейнера могут быть некорректные вычисления ограничений.
  • preventOverflow: Работает относительно boundariesElement. Если виртуальный элемент находится далеко от реальных элементов DOM, модификатор может сработать неожиданно.
  • offset: Необходимо задавать явно, особенно если виртуальный элемент имеет нулевую ширину и высоту, иначе сдвиг поппера может не соответствовать ожиданиям.
createPopper(virtualElement, tooltipElement, {
  placement: 'right',
  modifiers: [
    { name: 'offset', options: { offset: [0, 10] } },
    { name: 'preventOverflow', options: { boundary: document.body } },
  ],
});

Практические советы по работе с виртуальными элементами

  • Всегда возвращать полные свойства top, left, bottom, right, width, height в getBoundingClientRect.
  • Использовать виртуальные элементы для динамических или невидимых источников, таких как текстовые выделения, курсор мыши или анимированные элементы.
  • Ручное обновление позиции поппера через update() обязательно, если координаты виртуального элемента изменяются.
  • Для корректной работы с модификаторами ограничений тестировать расположение поппера на различных размерах экрана и при изменении размеров контейнера.

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