В библиотеке Floating UI под референсом понимается объект, относительно которого вычисляется позиция плавающего элемента (tooltip, dropdown, popover и т.д.). Обычно это DOM-элемент, например кнопка или ссылка. Однако существуют ситуации, когда привязка к реальному элементу невозможна или нецелесообразна. Для таких случаев используется виртуальный референс (virtual element).
Виртуальный референс — это объект, который имитирует поведение DOM-элемента, предоставляя координаты и геометрию через специальный интерфейс. Он не существует в DOM-дереве, но Floating UI работает с ним так же, как с обычным элементом.
Использование виртуального референса оправдано в следующих сценариях:
В этих случаях отсутствует конкретный DOM-узел, но есть координаты, к которым нужно привязать интерфейсный элемент.
Виртуальный референс должен реализовывать метод:
getBoundingClientRect(): DOMRect
Этот метод возвращает объект, аналогичный
Element.getBoundingClientRect(), содержащий:
x, ytop, left, right,
bottomwidth, heightМинимальный пример:
const virtualElement = {
getBoundingClientRect() {
return {
x: 100,
y: 200,
top: 200,
left: 100,
right: 100,
bottom: 200,
width: 0,
height: 0,
};
},
};
Функция computePosition принимает виртуальный элемент
так же, как обычный DOM-элемент:
import { computePosition } from '@floating-ui/dom';
computePosition(virtualElement, floatingElement).then(({ x, y }) => {
Object.assign(floatingElement.style, {
left: `${x}px`,
top: `${y}px`,
});
});
Floating UI не делает различий между реальным и виртуальным
референсом — важен только контракт метода
getBoundingClientRect.
Один из самых распространённых кейсов — отображение tooltip рядом с курсором:
let x = 0;
let y = 0;
const virtualElement = {
getBoundingClientRect() {
return {
x,
y,
top: y,
left: x,
right: x,
bottom: y,
width: 0,
height: 0,
};
},
};
document.addEventListener('mousemove', (event) => {
x = event.clientX;
y = event.clientY;
computePosition(virtualElement, tooltip).then(({ x, y }) => {
Object.assign(tooltip.style, {
left: `${x}px`,
top: `${y}px`,
});
});
});
Здесь координаты обновляются динамически, а виртуальный элемент всегда возвращает актуальное положение.
Виртуальный референс может изменять свои координаты в зависимости от состояния приложения:
Важно, что getBoundingClientRect вызывается каждый раз
при пересчёте позиции, поэтому он должен возвращать актуальные
значения.
При работе с текстом используется API Range:
const selection = window.getSelection();
const range = selection.getRangeAt(0);
const virtualElement = {
getBoundingClientRect() {
return range.getBoundingClientRect();
},
};
Это позволяет привязать всплывающий элемент к выделенному тексту, даже если он разбит на несколько DOM-узлов.
Виртуальные элементы полностью совместимы с middleware Floating UI:
offsetflipshiftarrowautoPlacementПример:
import { computePosition, offset, flip, shift } from '@floating-ui/dom';
computePosition(virtualElement, floatingElement, {
middleware: [
offset(8),
flip(),
shift(),
],
}).then(({ x, y }) => {
Object.assign(floatingElement.style, {
left: `${x}px`,
top: `${y}px`,
});
});
Несмотря на гибкость, есть ряд особенностей:
Отсутствие layout-контекста Floating UI не может учитывать родительские контейнеры, прокрутку и overflow так же точно, как с DOM-элементами.
Нет автоматического отслеживания изменений Нужно вручную вызывать перерасчёт позиции при изменении координат.
Отсутствие взаимодействия с ResizeObserver и IntersectionObserver
Для автоматического обновления позиции используется
autoUpdate, но с виртуальными элементами требуется явное
управление:
import { autoUpdate } from '@floating-ui/dom';
const cleanup = autoUpdate(
virtualElement,
floatingElement,
() => {
computePosition(virtualElement, floatingElement).then(({ x, y }) => {
Object.assign(floatingElement.style, {
left: `${x}px`,
top: `${y}px`,
});
});
}
);
Однако обновления всё равно зависят от внешних событий (например,
mousemove).
const virtualElement = {
getBoundingClientRect() {
const { x, y } = getCanvasCoordinates();
return {
x,
y,
top: y,
left: x,
right: x,
bottom: y,
width: 0,
height: 0,
};
},
};
Во время перетаскивания можно использовать текущую позицию курсора или объекта:
let dragPosition = { x: 0, y: 0 };
const virtualElement = {
getBoundingClientRect() {
return {
...dragPosition,
top: dragPosition.y,
left: dragPosition.x,
right: dragPosition.x,
bottom: dragPosition.y,
width: 0,
height: 0,
};
},
};
Виртуальный референс расширяет область применения Floating UI за пределы стандартных интерфейсов. Он позволяет работать с абстрактными точками, динамическими координатами и нестандартными источниками данных, что особенно важно для современных веб-приложений с высокой интерактивностью и кастомной графикой.