Позиционирование списка

Позиционирование списков — важный аспект интерфейсной архитектуры. В контексте библиотеки Radix UI списки часто используются внутри таких компонентов, как:

  • выпадающие меню
  • списки выбора
  • контекстные меню
  • автокомплит
  • popover-элементы
  • комбобоксы

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

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


Архитектура позиционирования в Radix UI

Позиционирование списка строится вокруг нескольких базовых элементов:

Элемент Назначение
Trigger элемент, вызывающий появление списка
Content контейнер со списком
Portal перенос списка в корень DOM
Viewport область отображения элементов списка
Item отдельный элемент списка

Простейшая структура выпадающего списка:

import * as Sel ect from "@radix-ui/react-select";

<Select.Root>
  <Select.Trigger>
    <Select.Value />
  </Select.Trigger>

  <Select.Portal>
    <Select.Content>
      <Select.Viewport>
        <Select.Item value="one">
          <Select.ItemText>One</Select.ItemText>
        </Select.Item>
      </Select.Viewport>
    </Select.Content>
  </Select.Portal>
</Select.Root>

Контейнер Select.Content является ключевым элементом, отвечающим за позиционирование.


Стратегии позиционирования

Radix UI поддерживает две основные стратегии позиционирования:

item-aligned

Позиционирование относительно выбранного элемента списка.

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

Пример:

<Select.Content position="item-aligned">

Особенности:

  • выбранный элемент совмещается с триггером
  • удобство при длинных списках
  • естественное поведение для dropdown-select

popper

Позиционирование с использованием механизма Floating UI.

<Select.Content position="popper">

Особенности:

  • список размещается относительно триггера
  • автоматически учитываются границы окна
  • поддерживаются смещения и выравнивания

Этот режим обеспечивает более гибкий контроль над расположением элементов.


Свойство side

Параметр side определяет сторону, относительно которой появляется список.

Возможные значения:

  • top
  • bottom
  • left
  • right

Пример:

<Select.Content side="bottom">

Поведение:

  • список отображается снизу от триггера
  • при нехватке пространства возможен автоматический перенос

Выравнивание списка

Параметр align управляет горизонтальным или вертикальным выравниванием.

Возможные значения:

  • start
  • center
  • end

Пример:

<Select.Content
  side="bottom"
  align="start"
>

Результат:

  • левый край списка выравнивается по левому краю триггера.

Смещение списка

Для тонкой настройки расположения используются параметры смещения.

sideOffset

Расстояние между триггером и списком.

<Select.Content
  side="bottom"
  sideOffset={8}
>

Эффект:

  • список появляется на 8 пикселей ниже триггера.

alignOffset

Смещение вдоль оси выравнивания.

<Select.Content
  align="start"
  alignOffset={10}
>

Применяется для точной подстройки позиции.


Предотвращение выхода за границы экрана

Floating UI автоматически предотвращает выход списка за пределы видимой области.

В Radix UI это поведение управляется параметром:

avoidCollisions

Пример:

<Select.Content
  avoidCollisions
>

Механизм работает следующим образом:

  1. вычисляется свободное пространство вокруг триггера
  2. если выбранная сторона не помещается
  3. список переносится на противоположную сторону

Отступы от границ окна

Параметр collisionPadding задаёт безопасную зону от края окна.

<Select.Content
  collisionPadding={10}
>

Список никогда не будет расположен ближе чем на 10px к границе viewport.


Ограничение области вычисления

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

Для этого используется параметр:

collisionBoundary

Пример:

<Select.Content
  collisionBoundary={document.body}
>

Допустимо передавать:

  • DOM-элемент
  • массив элементов
  • ref

Использование порталов

Списки часто находятся внутри контейнеров с overflow: hidden или z-index. Это может ломать позиционирование.

Radix UI решает проблему с помощью компонента Portal.

<Select.Portal>
  <Select.Content>

Портал перемещает список:

  • в конец body
  • вне текущего DOM-контекста

Это предотвращает:

  • обрезание контента
  • проблемы со stacking context
  • конфликт z-index.

Управление размером списка

Radix UI предоставляет CSS-переменные для адаптации размеров.

Пример:

.SelectContent {
  width: var(--radix-select-trigger-width);
}

Доступные переменные:

Переменная Назначение
--radix-select-trigger-width ширина триггера
--radix-select-content-available-height доступная высота
--radix-select-content-transform-origin точка трансформации

Пример ограничения высоты:

.SelectContent {
  max-height: var(--radix-select-content-available-height);
}

Позиционирование Viewport

Компонент Viewport содержит сами элементы списка.

<Select.Viewport>

Он:

  • ограничивает область прокрутки
  • управляет виртуальной высотой списка
  • оптимизирует отрисовку

Пример:

<Select.Content>
  <Select.Viewport className="SelectViewport">
    {items}
  </Select.Viewport>
</Select.Content>

CSS:

.SelectViewport {
  padding: 5px;
}

Скролл внутри списка

Для длинных списков используются специальные элементы прокрутки:

<Select.ScrollUpButton />
<Select.ScrollDownButton />

Структура:

<Select.Content>

  <Select.ScrollUpButton>
    ▲
  </Select.ScrollUpButton>

  <Select.Viewport>
    ...
  </Select.Viewport>

  <Select.ScrollDownButton>
    ▼
  </Select.ScrollDownButton>

</Select.Content>

Эти элементы:

  • появляются автоматически
  • обеспечивают плавную прокрутку
  • поддерживают клавиатурную навигацию.

Анимации появления

Radix UI передаёт состояние позиционирования через атрибуты:

data-state="open"
data-side="top"
data-side="bottom"

Пример CSS-анимации:

.SelectContent[data-state="open"] {
  animation: fadeIn 120ms ease-out;
}

.SelectContent[data-side="top"] {
  animation: slideDown 120ms ease-out;
}

.SelectContent[data-side="bottom"] {
  animation: slideUp 120ms ease-out;
}

Пример keyframes:

@keyframes slideUp {
  fr om {
    opacity: 0;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

Анимации автоматически адаптируются к стороне появления.


Адаптация к изменению размера окна

Floating UI автоматически пересчитывает координаты при:

  • изменении размеров окна
  • прокрутке страницы
  • изменении размеров контента

Это обеспечивает корректное поведение:

  • popover
  • dropdown
  • select
  • tooltip.

Управление transform-origin

Radix UI передаёт точку трансформации через CSS-переменную:

--radix-select-content-transform-origin

Пример использования:

.SelectContent {
  transform-origin: var(--radix-select-content-transform-origin);
}

Это позволяет создавать естественные анимации масштабирования.


Типичная структура позиционирования

Реальная структура выпадающего списка обычно выглядит так:

<Select.Root>

  <Select.Trigger>
    <Select.Value />
  </Select.Trigger>

  <Select.Portal>

    <Select.Content
      side="bottom"
      align="start"
      sideOffset={6}
      collisionPadding={10}
    >

      <Select.ScrollUpButton />

      <Select.Viewport>
        <Select.Item value="a">
          <Select.ItemText>A</Select.ItemText>
        </Select.Item>

        <Select.Item value="b">
          <Select.ItemText>B</Select.ItemText>
        </Select.Item>
      </Select.Viewport>

      <Select.ScrollDownButton />

    </Select.Content>

  </Select.Portal>

</Select.Root>

Такой подход обеспечивает:

  • корректное позиционирование
  • адаптацию к границам окна
  • поддержку длинных списков
  • независимость от DOM-контекста
  • удобную кастомизацию через CSS.