Floating UI — это библиотека для позиционирования плавающих элементов в браузере, таких как тултипы, модальные окна, меню и dropdown. Она обеспечивает точное управление положением элементов относительно их опорных точек, учитывая размеры окна, прокрутку, ограничения видимой области и перекрытия.
Dropdown-компонент представляет собой элемент, который раскрывается при взаимодействии с триггером (кнопкой или ссылкой) и отображает список опций. Floating UI позволяет создавать динамичные, адаптивные и безопасно позиционируемые dropdown-меню.
Для работы с Floating UI необходимо установить пакет:
npm install @floating-ui/dom
Импортируем необходимые функции:
import { computePosition, offset, flip, shift, autoUpdate } from '@floating-ui/dom';
Dropdown состоит из двух основных частей:
button, a или div).ul/div), который позиционируется относительно
триггера.Пример базовой разметки:
<button id="dropdown-trigger">Меню</button>
<ul id="dropdown-menu" style="display: none;">
<li>Опция 1</li>
<li>Опция 2</li>
<li>Опция 3</li>
</ul>
Для открытия dropdown и корректного позиционирования используется
computePosition:
const trigger = document.getElementById('dropdown-trigger');
const menu = document.getElementById('dropdown-menu');
function openDropdown() {
menu.style.display = 'block';
computePosition(trigger, menu, {
placement: 'bottom-start',
middleware: [
offset(5),
flip(),
shift({ padding: 10 })
]
}).then(({ x, y }) => {
Object.assign(menu.style, {
left: `${x}px`,
top: `${y}px`
});
});
}
placement — направление относительно триггера
(top, bottom, left,
right) с уточнением (start,
end).
middleware — цепочка промежуточных функций, которые корректируют позицию.
offset добавляет смещение.flip изменяет направление при нехватке места.shift с padding предотвращает выход элемента за границы
видимой области.Чтобы меню оставалось корректно позиционированным при изменении
размеров окна или скролле, используется autoUpdate:
const cleanup = autoUpdate(trigger, menu, () => {
computePosition(trigger, menu, {
placement: 'bottom-start',
middleware: [offset(5), flip(), shift({ padding: 10 })]
}).then(({ x, y }) => {
Object.assign(menu.style, { left: `${x}px`, top: `${y}px` });
});
});
// Для закрытия dropdown:
function closeDropdown() {
menu.style.display = 'none';
cleanup(); // отключаем автоматическое обновление
}
autoUpdate автоматически отслеживает:
Dropdown обычно открывается по клику на триггер, а закрывается при повторном клике, выборе опции или клике вне меню:
trigger.addEventListener('click', () => {
if (menu.style.display === 'block') {
closeDropdown();
} else {
openDropdown();
}
});
document.addEventListener('click', (event) => {
if (!trigger.contains(event.target) && !menu.contains(event.target)) {
closeDropdown();
}
});
Floating UI позволяет создавать dropdown, который адаптируется к ограниченному пространству. Например:
computePosition(trigger, menu, {
placement: 'bottom-start',
middleware: [
offset(5),
flip({ fallbackPlacements: ['top-start', 'right-start', 'left-start'] }),
shift({ padding: 10 })
]
});
fallbackPlacements задаёт список альтернативных
направлений, если основное не помещается.shift обеспечивает плавное смещение в пределах
окна.Поскольку Floating UI управляет только позицией, анимация открытия dropdown выполняется через CSS:
#dropdown-menu {
transition: opacity 0.2s ease, transform 0.2s ease;
opacity: 0;
transform: scaleY(0);
transform-origin: top;
}
#dropdown-menu.show {
opacity: 1;
transform: scaleY(1);
}
И синхронизация с JS:
function openDropdown() {
menu.style.display = 'block';
menu.classList.add('show');
computePosition(trigger, menu, { placement: 'bottom-start', middleware: [offset(5), flip(), shift({ padding: 10 })] })
.then(({ x, y }) => Object.assign(menu.style, { left: `${x}px`, top: `${y}px` }));
}
function closeDropdown() {
menu.classList.remove('show');
setTimeout(() => menu.style.display = 'none', 200);
}
Floating UI позволяет строить вложенные меню, где каждый уровень позиционируется относительно родительской опции:
const parentOption = document.querySelector('.parent-option');
const subMenu = document.querySelector('.sub-menu');
parentOption.addEventListener('mouseenter', () => {
computePosition(parentOption, subMenu, {
placement: 'right-start',
middleware: [offset(2), flip(), shift({ padding: 5 })]
}).then(({ x, y }) => Object.assign(subMenu.style, { left: `${x}px`, top: `${y}px` }));
subMenu.style.display = 'block';
});
parentOption.addEventListener('mouseleave', () => {
subMenu.style.display = 'none';
});
Dropdown должен быть доступным для клавиатурной навигации:
tabindex="0" для опций;ArrowUp,
ArrowDown);Escape.menu.addEventListener('keydown', (event) => {
const items = Array.from(menu.querySelectorAll('li'));
let index = items.indexOf(document.activeElement);
if (event.key === 'ArrowDown') {
index = (index + 1) % items.length;
items[index].focus();
} else if (event.key === 'ArrowUp') {
index = (index - 1 + items.length) % items.length;
items[index].focus();
} else if (event.key === 'Escape') {
closeDropdown();
trigger.focus();
}
});
Floating UI можно использовать с React, Vue или Svelte через адаптеры
или напрямую, создавая компоненты с реактивным состоянием открытия и
закрытия, и синхронизацией позиции через
computePosition.
В React пример минимального dropdown:
import { useRef, useState, useEffect } from 'react';
import { computePosition, offset, flip, shift, autoUpdate } from '@floating-ui/dom';
function Dropdown() {
const [open, setOpen] = useState(false);
const triggerRef = useRef(null);
const menuRef = useRef(null);
useEffect(() => {
if (open && triggerRef.current && menuRef.current) {
const cleanup = autoUpdate(triggerRef.current, menuRef.current, () => {
computePosition(triggerRef.current, menuRef.current, {
placement: 'bottom-start',
middleware: [offset(5), flip(), shift({ padding: 10 })]
}).then(({ x, y }) => Object.assign(menuRef.current.style, { left: `${x}px`, top: `${y}px` }));
});
return cleanup;
}
}, [open]);
return (
<>
<button ref={triggerRef} onCl ick={() => setOpen(!open)}>Меню</button>
<ul ref={menuRef} style={{ display: open ? 'block' : 'none' }}>
<li>Опция 1</li>
<li>Опция 2</li>
</ul>
</>
);
}
Этот подход обеспечивает чистое управление состоянием и точное позиционирование даже при изменении размера окна или содержимого dropdown.
Floating UI предоставляет мощные инструменты для создания полностью кастомизируемых dropdown-меню с точной поддержкой позиционирования, адаптивности, анимации и доступности, позволяя реализовать сложные пользовательские интерфейсы без ручного расчёта координат и учета множества граничных случаев.