Inline-элементы (например, <span>,
<a>, текстовые узлы) ведут себя иначе, чем блочные.
Их геометрия может быть фрагментированной: один логический элемент
способен занимать несколько строк, а значит — иметь несколько
прямоугольников отображения.
Стандартный метод getBoundingClientRect() возвращает
единый ограничивающий прямоугольник, который охватывает
все строки inline-элемента. Это приводит к ряду проблем:
Для точного позиционирования Floating UI использует более гибкий подход — работу с ClientRect-коллекциями.
Метод element.getClientRects() возвращает список
прямоугольников (DOMRectList), каждый из которых
соответствует отдельному фрагменту inline-контента:
const rects = element.getClientRects();
Каждый прямоугольник описывает:
x, y — координаты относительно
viewport;width, height — размеры;Это особенно важно в случаях:
line-height и
letter-spacing);Floating UI абстрагирует работу с DOM и позволяет передавать
виртуальные элементы, реализующие метод
getBoundingClientRect.
Для inline-контента создаётся объект, который вместо одного прямоугольника может использовать наиболее релевантный fragment.
Пример виртуального элемента:
const virtualElement = {
getBoundingClientRect() {
const rects = element.getClientRects();
return rects[0]; // или другой выбранный rect
}
};
Однако выбор rects[0] — не универсальное решение.
Floating UI предоставляет утилиту inline(), которая
автоматически определяет, какой из ClientRect
использовать.
Подключение middleware:
import {computePosition, inline} from '@floating-ui/dom';
computePosition(referenceEl, floatingEl, {
middleware: [inline()]
});
inline():ClientRect;Для интерактивных сценариев (например, hover по тексту) важно учитывать координаты курсора.
Floating UI позволяет передать координаты события:
let mouseX = 0;
let mouseY = 0;
element.addEventListener('mousemove', (event) => {
mouseX = event.clientX;
mouseY = event.clientY;
});
Затем inline() использует эти координаты для выбора
нужного fragment.
Для более точного контроля над текстом можно использовать
Range:
const range = document.createRange();
range.setStart(textNode, startOffset);
range.setEnd(textNode, endOffset);
const rects = range.getClientRects();
Это позволяет:
Floating UI может работать с таким диапазоном через виртуальный элемент:
const virtualElement = {
getBoundingClientRect() {
return range.getBoundingClientRect();
},
getClientRects() {
return range.getClientRects();
}
};
getBoundingClientRect() подходит:
getClientRects() обязателен:
Некоторые rect’ы могут иметь нулевые размеры:
if (rect.width === 0 || rect.height === 0) {
// игнорировать
}
Координаты возвращаются относительно viewport, поэтому при вычислениях важно учитывать прокрутку:
const absoluteX = rect.left + window.scrollX;
const absoluteY = rect.top + window.scrollY;
В правосторонних языках (direction: rtl) порядок rect’ов
может отличаться. Необходимо учитывать это при ручной обработке.
Пример полной интеграции:
import {computePosition, inline, offset, flip, shift} from '@floating-ui/dom';
computePosition(referenceEl, floatingEl, {
middleware: [
inline(),
offset(8),
flip(),
shift()
]
}).then(({x, y}) => {
Object.assign(floatingEl.style, {
left: `${x}px`,
top: `${y}px`
});
});
Здесь:
inline() отвечает за корректный выбор ClientRect;Range;getClientRects;<a>;inline() гарантирует привязку к нужной строке.Иногда требуется собственный алгоритм:
function getClosestRect(rects, x, y) {
let minDistance = Infinity;
let closestRect = null;
for (const rect of rects) {
const dx = rect.left - x;
const dy = rect.top - y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < minDistance) {
minDistance = distance;
closestRect = rect;
}
}
return closestRect;
}
Использование:
const virtualElement = {
getBoundingClientRect() {
const rects = element.getClientRects();
return getClosestRect(rects, mouseX, mouseY);
}
};
Работа с getClientRects() может быть дороже, чем с
getBoundingClientRect(), особенно при:
mousemove).Оптимизации:
requestAnimationFrame);ResizeObserver и
IntersectionObserver.Определение типа элемента (inline vs block)
Получение ClientRect (если inline)
Выбор нужного fragment:
inline() middleware;Передача виртуального элемента в Floating UI
Применение middleware для позиционирования
Такой подход обеспечивает точное, предсказуемое и визуально корректное позиционирование floating-элементов даже в сложных текстовых интерфейсах.