Date picker позиционирование

Popper.js — это мощная библиотека для управления позиционированием всплывающих элементов относительно целевых элементов на странице. В контексте date picker она используется для корректного отображения календаря рядом с полем ввода даты, с учётом видимой области окна браузера и возможных смещений.


Создание Popper для Date Picker

Для начала необходимо импортировать Popper.js и инициализировать экземпляр Popper:

import { createPopper } from '@popperjs/core';

const input = document.querySelector('#date-input');
const calendar = document.querySelector('#calendar');

const popperInstance = createPopper(input, calendar, {
  placement: 'bottom-start',
  modifiers: [
    {
      name: 'offset',
      options: {
        offset: [0, 8], // смещение по оси X и Y
      },
    },
    {
      name: 'preventOverflow',
      options: {
        boundary: 'viewport', // предотвращение выхода за видимую область
      },
    },
    {
      name: 'flip',
      options: {
        fallbackPlacements: ['top-start', 'right-start'], // варианты размещения при нехватке места
      },
    },
  ],
});

Ключевые моменты:

  • placement определяет основное положение календаря относительно поля ввода. Например, bottom-start размещает календарь снизу слева.
  • Модификатор offset позволяет добавить дополнительное расстояние между полем ввода и календарем.
  • Модификатор preventOverflow гарантирует, что календарь не выйдет за границы видимой области.
  • Модификатор flip автоматически меняет позицию календаря, если изначальная не помещается.

Динамическое обновление позиции

Date picker часто открывается и закрывается при кликах или фокусе. Для корректного позиционирования при изменении размеров окна или скролле необходимо вызывать метод update:

input.addEventListener('focus', () => {
  calendar.style.display = 'block';
  popperInstance.update();
});

window.addEventListener('resize', () => {
  popperInstance.update();
});

Метод update пересчитывает позицию календаря относительно поля ввода и учитывает все модификаторы.


Адаптивное размещение с ограничением по высоте

Иногда календарь должен размещаться внутри ограниченного контейнера. Можно использовать модификатор preventOverflow с параметром padding, чтобы добавить отступы от границ:

{
  name: 'preventOverflow',
  options: {
    boundary: '#datepicker-container',
    padding: 10,
  },
}

Это предотвращает частичное обрезание календаря и учитывает внутренние отступы контейнера.


Применение с анимацией открытия

Для плавного появления календаря важно синхронизировать анимацию с обновлением позиции:

input.addEventListener('focus', () => {
  calendar.classList.add('show'); // CSS анимация
  popperInstance.update();
});

input.addEventListener('blur', () => {
  calendar.classList.remove('show');
});

Использование update гарантирует, что позиция будет пересчитана перед началом анимации, предотвращая дергание элемента.


Продвинутая настройка с кастомными смещениями

Можно реализовать смещение не только по осям, но и с учетом размеров календаря:

const customOffset = {
  name: 'offset',
  options: {
    offset: ({ reference, popper }) => {
      const x = 0;
      const y = reference.height + 4; // 4px дополнительного отступа
      return [x, y];
    },
  },
};

Такой подход позволяет динамически изменять расстояние в зависимости от размеров поля ввода или календаря.


Контроль видимости при скролле

Для долгих страниц календарь может уехать за пределы видимой области. Модификатор preventOverflow вместе с flip обеспечивает автоматический перенос:

modifiers: [
  {
    name: 'flip',
    options: {
      fallbackPlacements: ['top-start', 'bottom-end'],
    },
  },
  {
    name: 'preventOverflow',
    options: {
      mainAxis: true,
      altAxis: true,
    },
  },
]
  • mainAxis контролирует смещение по основной оси (например, вертикально для bottom-start).
  • altAxis контролирует смещение по противоположной оси, предотвращая горизонтальное обрезание.

Интеграция с динамическим контентом

Если календарь содержит динамические элементы (например, смена месяцев), необходимо пересчитывать позицию после каждого изменения:

function renderMonth() {
  calendar.innerHTML = generateMonthHTML();
  popperInstance.update(); // пересчет позиции после изменения содержимого
}

Это гарантирует, что Popper всегда корректно учитывает новые размеры календаря.


Советы по производительности

  • Использовать strategy: 'fixed' для календарей, которые не должны смещаться при скролле родителя.
  • Минимизировать частоту вызова update при прокрутке с помощью requestAnimationFrame.
  • Группировать модификаторы, чтобы избежать конфликтов между offset, flip и preventOverflow.

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