Позиционирование относительно триггера

Radix UI предоставляет мощный и гибкий механизм для позиционирования всплывающих элементов, таких как Popover, Dropdown Menu, Tooltip и других компонентов, которые должны быть привязаны к определённому элементу — триггеру. Правильное позиционирование критично для UX: оно гарантирует, что интерактивные элементы не перекрывают важный контент, корректно отображаются на любых разрешениях экрана и учитывают динамическое изменение размеров окна.

Основы позиционирования

В Radix UI позиционирование реализуется через свойства side, align, sideOffset и alignOffset, которые управляют расположением контентного блока относительно триггера.

  • side — указывает сторону, на которой должен появляться контент. Допустимые значения:

    • top — над триггером
    • bottom — под триггером
    • left — слева от триггера
    • right — справа от триггера
  • align — определяет выравнивание контента по выбранной стороне:

    • start — выравнивание по началу (левый край для top/bottom, верхний край для left/right)
    • center — выравнивание по центру
    • end — выравнивание по концу (правый край для top/bottom, нижний край для left/right)
  • sideOffset — смещение от триггера по выбранной стороне. Положительное значение отдаляет контент от триггера, отрицательное — приближает.

  • alignOffset — смещение вдоль оси выравнивания. Позволяет точно подгонять контент по горизонтали или вертикали относительно триггера.

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

<Popover.Root>
  <Popover.Trigger>Открыть</Popover.Trigger>
  <Popover.Content side="bottom" align="start" sideOffset={10} alignOffset={5}>
    Контент поповера
  </Popover.Content>
</Popover.Root>

В этом примере поповер появится под триггером, с небольшим отступом и смещением по горизонтали.

Динамическое позиционирование и fallback

Radix UI использует библиотеку @radix-ui/popper под капотом, что позволяет автоматически корректировать позицию при недостатке места на экране. Для этого применяются свойства:

  • collisionPadding — минимальное расстояние до границ окна. Контент не будет выезжать за пределы видимой области.
  • avoidCollisions — включает механизм обхода препятствий. Если контент не помещается в заданной позиции, Radix автоматически подбирает альтернативную сторону.
  • fallbackSides — массив сторон, которые могут быть использованы в случае нехватки места. Например:
<Popover.Content side="bottom" fallbackSides={['top', 'right', 'left']} sideOffset={8}>
  Контент с fallback
</Popover.Content>

Контроль трансформаций и анимаций

Для визуальной плавности перемещения контента применяется transform-origin, который автоматически подстраивается под выбранное положение:

  • При side="top" origin находится внизу контента.
  • При side="left" origin справа и т.д.

Это позволяет создавать анимации появления, которые выглядят естественно, например, scale или fade.

[data-state='open'] {
  animation: fadeIn 200ms ease-out, scaleIn 200ms ease-out;
}

@keyframes scaleIn {
  0% { transform: scale(0.95); }
  100% { transform: scale(1); }
}

Комбинированное использование с scroll и viewport

Для компонентов, которые должны оставаться привязанными к триггеру даже при прокрутке, Radix поддерживает свойство sticky:

  • sticky — фиксирует позицию относительно триггера при скролле контейнера.
  • portalled — позволяет рендерить контент в body, что помогает избежать обрезки родительскими контейнерами с overflow: hidden.
<Popover.Content side="right" sticky sideOffset={12} portalled>
  Контент, остающийся видимым при прокрутке
</Popover.Content>

Особенности работы с Nested Trigger

При использовании вложенных триггеров, например в Dropdown Menu, важно учитывать иерархию:

  • Вложенный контент наследует координаты родительского триггера.
  • Для каждого уровня можно отдельно задавать side, align и offset.
  • Radix автоматически рассчитывает смещения, чтобы избежать перекрытий между соседними всплывающими блоками.

Практические рекомендации

  • Для элементов с динамическим контентом использовать sideOffset ≥ 8px, чтобы избежать наложений на триггер.
  • Всегда задавать fallbackSides, чтобы компонент корректно отображался на маленьких экранах.
  • При сложных интерфейсах использовать portalled и sticky, чтобы избежать проблем с overflow и скроллом.
  • Для анимаций применять transform-origin, чтобы движение контента выглядело естественно, не “выстреливало” от центра окна.

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