Стратегия выбора лучшей позиции

Popper.js — библиотека для точного позиционирования всплывающих элементов (tooltip, dropdown, popover) относительно опорного элемента (reference). Основная задача — выбрать такую позицию, при которой всплывающий элемент остаётся видимым, не перекрывает критичный контент и соответствует UX-ожиданиям.

Ключевым понятием является placement — начальное желаемое расположение:

  • top, bottom, left, right
  • вариации: top-start, top-end и т.д.

Однако реальная позиция редко остаётся фиксированной. В процессе вычислений Popper применяет стратегию адаптации с помощью модификаторов.


Факторы, влияющие на выбор позиции

1. Размеры элементов

  • Размер reference-элемента
  • Размер popper-элемента
  • Соотношение между ними

Крупный popper требует больше пространства и чаще вызывает смещение.

2. Границы контейнера (boundary)

По умолчанию используется viewport, но можно задать:

  • родительский контейнер
  • кастомный DOM-элемент

Это влияет на поведение при переполнении.

3. Скролл и overflow

Если контейнер имеет overflow: hidden | auto | scroll, Popper учитывает:

  • видимую область
  • clipping parents (цепочку обрезающих контейнеров)

4. Отступы и padding

Дополнительные ограничения, влияющие на минимальное расстояние до границ.


Базовая стратегия: предпочтительная позиция + fallback

Popper не просто ставит элемент в заданную позицию. Он использует стратегию:

  1. Применить предпочтительное placement
  2. Проверить, выходит ли элемент за границы
  3. Если да — выбрать альтернативное положение

Пример:

createPopper(reference, popper, {
  placement: 'top',
  modifiers: [
    {
      name: 'flip',
      options: {
        fallbackPlacements: ['bottom', 'right', 'left']
      }
    }
  ]
});

Модификатор flip: динамическая смена позиции

Основной инструмент адаптации.

Принцип работы:

  • Проверяет, помещается ли popper в текущем положении
  • Если нет — перебирает fallback-позиции
  • Выбирает первую подходящую

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

  • Учитывает boundaries
  • Работает вместе с overflow detection

Гибкая настройка:

{
  name: 'flip',
  options: {
    fallbackPlacements: ['bottom-start', 'right-end'],
    padding: 8,
    boundary: document.body
  }
}

Модификатор preventOverflow: удержание внутри границ

Если flip отвечает за смену стороны, то preventOverflow — за точную подстройку.

Задачи:

  • Смещение popper внутрь видимой области
  • Минимизация выхода за границы
{
  name: 'preventOverflow',
  options: {
    mainAxis: true,
    altAxis: true,
    tether: true
  }
}

Параметры:

  • mainAxis — контроль по основной оси (например, по вертикали для top/bottom)
  • altAxis — контроль по перпендикулярной оси
  • tether — ограничивает отрыв popper от reference

Стратегии выбора позиции

1. Стратегия “жёсткого предпочтения”

Используется, когда важно строгое положение:

placement: 'bottom',
modifiers: [
  { name: 'flip', enabled: false }
]

Применение:

  • UI с фиксированной геометрией
  • контролируемые контейнеры

2. Стратегия “адаптивного выбора”

Наиболее распространённый вариант:

placement: 'top',
modifiers: [
  {
    name: 'flip',
    options: {
      fallbackPlacements: ['bottom', 'right', 'left']
    }
  }
]

Плюсы:

  • гибкость
  • устойчивость к изменениям layout

3. Стратегия “минимального смещения”

Приоритет — сохранить положение, даже если часть popper выходит за границы:

{
  name: 'preventOverflow',
  options: {
    tether: false
  }
}

Применение:

  • tooltips
  • визуальные подсказки

4. Стратегия “лучшего видимого пространства”

Использование кастомной логики выбора:

{
  name: 'flip',
  options: {
    fallbackPlacements: ['top', 'bottom', 'right', 'left']
  }
}

Popper выбирает позицию с максимальной доступной площадью.


Осознанный выбор fallback-позиций

Порядок fallback-значений критичен:

fallbackPlacements: ['top', 'right', 'left']

Рекомендации:

  • сначала противоположная сторона (top → bottom)
  • затем боковые
  • учитывать направление интерфейса (LTR / RTL)

Влияние вариаций (start, end)

Placement может быть уточнён:

  • top-start
  • bottom-end

Поведение:

  • start — выравнивание по началу reference
  • end — по концу

При нехватке места Popper может:

  • изменить только сторону
  • или также изменить выравнивание

Работа с viewport и scroll-контейнерами

Popper анализирует:

  • viewport
  • clipping parents

Можно явно задать границу:

{
  name: 'preventOverflow',
  options: {
    boundary: 'viewport'
  }
}

или

boundary: document.querySelector('.container')

Стратегия fixed vs absolute

Popper поддерживает два режима позиционирования:

absolute (по умолчанию)

  • зависит от ближайшего positioned-родителя
  • быстрее
  • подходит для большинства случаев

fixed

  • относительно viewport
  • игнорирует scroll контейнеры
strategy: 'fixed'

Когда использовать:

  • всплывающие окна поверх всей страницы
  • модальные интерфейсы

Смещение (offset) как часть стратегии

Позволяет корректировать итоговую позицию:

{
  name: 'offset',
  options: {
    offset: [0, 10]
  }
}
  • первый параметр — по основной оси
  • второй — по поперечной

Используется для:

  • визуального отступа
  • предотвращения “слипания”

Кастомные стратегии выбора позиции

Popper допускает расширение через собственные модификаторы.

Пример логики:

  • измерить доступное пространство с каждой стороны
  • выбрать сторону с максимальной площадью
const customModifier = {
  name: 'customPlacement',
  enabled: true,
  phase: 'main',
  fn({ state }) {
    // анализ размеров и изменение placement
  }
};

Оптимизация производительности

Частое пересчитывание позиции может быть дорогим:

  • resize
  • scroll
  • DOM mutations

Решения:

  • отключение лишних модификаторов
  • debounce обновлений
  • ручной контроль update()

Типичные ошибки при выборе позиции

1. Игнорирование overflow

  • popper уходит за экран

2. Неправильный порядок fallback

  • нелогичное поведение

3. Отсутствие offset

  • визуальные артефакты

4. Неверный boundary

  • неожиданные обрезания

Практическая схема выбора стратегии

  1. Определить приоритетную сторону (placement)
  2. Задать fallback-цепочку (flip)
  3. Ограничить выход за границы (preventOverflow)
  4. Добавить отступ (offset)
  5. При необходимости — выбрать стратегию (fixed/absolute)
  6. Оптимизировать под конкретный layout

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