Анимации и переходы

Работа анимаций в Flatpickr основана на сочетании CSS-переходов и минимального JavaScript-контроля состояния календаря. Библиотека не использует тяжёлые JS-анимационные движки, а полагается на классы состояния, которые переключаются при открытии, закрытии и смене внутренних представлений (месяц, год, режим времени). Это делает поведение предсказуемым и легко переопределяемым через стили.

Основная логика анимаций в Flatpickr строится вокруг переключения состояния календаря:

  • открытие календаря
  • закрытие календаря
  • переключение месяцев
  • изменение режима (date / time / datetime)
  • мобильная адаптация

Каждое состояние отражается через CSS-классы, которые добавляются и удаляются в DOM. Сам JavaScript не выполняет сложную интерполяцию значений, а только инициирует переход состояний.

Ключевой принцип:

анимация = CSS transition + класс состояния

Параметр animate и глобальное управление переходами

В конфигурации Flatpickr присутствует опция:

animate: true

Она определяет, будут ли применяться переходы открытия/закрытия календаря.

Отключение анимаций

flatpickr("#input", {
  animate: false
});

При отключении:

  • мгновенное появление календаря
  • отсутствие transition-эффектов
  • минимальная нагрузка на repaint

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

Открытие и закрытие календаря

При взаимодействии с полем ввода Flatpickr добавляет/убирает состояния календаря.

Типичная последовательность:

Открытие

  1. Добавляется элемент календаря в DOM (если он ещё не создан)
  2. Применяется класс состояния открытия
  3. Срабатывают CSS transitions

Закрытие

  1. Удаляется класс активного состояния
  2. Запускается transition на opacity/transform
  3. После завершения анимации DOM-узел скрывается

Часто используется комбинация:

  • opacity
  • transform: translateY
  • pointer-events

CSS-основа анимаций

Хотя Flatpickr не навязывает стили, стандартная тема использует следующие принципы:

.flatpickr-calendar {
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 150ms ease, transform 150ms ease;
}

.flatpickr-calendar.open {
  opacity: 1;
  transform: translateY(0);
}

Важный момент

Анимация не привязана к JavaScript-таймерам. Всё завязано на CSS transition end.

Переходы между месяцами

Навигация по месяцам в Flatpickr реализована через смену внутреннего состояния календаря.

При переходе:

  • пересчитывается сетка дней
  • обновляется DOM-структура
  • применяется лёгкий fade или slide эффект (в зависимости от темы)

Часто используется стратегия:

  • старый месяц исчезает (opacity: 0)
  • новый появляется (opacity: 1)

или

  • сдвиг по оси X
.dayContainer {
  transition: transform 120ms ease, opacity 120ms ease;
}

Анимация выбора даты

При выборе даты в Flatpickr происходит обновление состояния активного элемента.

Типичный визуальный эффект:

  • подсветка выбранного дня
  • краткий transition background-color
  • обновление диапазонов (если включён range mode)
.flatpickr-day.selected {
  transition: background-color 120ms ease, border-color 120ms ease;
}

Range-режим и сложные переходы

В режиме диапазона анимации становятся более заметными, так как изменяются сразу несколько состояний:

  • начало диапазона
  • конец диапазона
  • промежуточные дни

Flatpickr синхронно обновляет классы:

  • inRange
  • startRange
  • endRange

Это позволяет CSS плавно интерполировать фон:

.flatpickr-day.inRange {
  transition: background-color 120ms ease;
}

Переходы при смене времени

Если включён time picker, Flatpickr применяет отдельные transition-эффекты для:

  • часов
  • минут
  • переключения AM/PM

Основной принцип — минимальная анимация, чтобы избежать дерганий интерфейса:

.numInputWrapper input {
  transition: color 100ms ease;
}

Использование JavaScript-хуков для управления анимацией

Хотя анимации в Flatpickr в основном CSS-ориентированные, поведение можно расширять через хуки:

  • onOpen
  • onClose
  • onMonthChange
  • onYearChange

Пример управления классами:

flatpickr("#input", {
  onOpen: function(selectedDates, dateStr, instance) {
    instance.calendarContainer.classList.add("custom-open");
  },
  onClose: function(selectedDates, dateStr, instance) {
    instance.calendarContainer.classList.remove("custom-open");
  }
});

Это позволяет добавлять:

  • дополнительные keyframe-анимации
  • задержки переходов
  • синхронизацию с внешними компонентами

Кастомные keyframes

При необходимости можно полностью переопределить визуальное поведение календаря Flatpickr через @keyframes:

@keyframes fpFadeIn {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.flatpickr-calendar.open {
  animation: fpFadeIn 180ms ease;
}

Важно учитывать, что при использовании animation вместо transition необходимо контролировать:

  • повторные открытия календаря
  • сброс состояния animation
  • конфликт с встроенными transition

Производительность анимаций

Flatpickr оптимизирован для лёгких UI-операций, поэтому анимации должны опираться на свойства:

  • transform
  • opacity

Не рекомендуется использовать:

  • top / left (вызывает layout recalculation)
  • height / width (дорогие reflow операции)

Правильный подход:

/* оптимально */
transform: translateY(0);

/* нежелательно */
top: 0;

Мобильные переходы

На мобильных устройствах Flatpickr часто переключается в упрощённый режим отображения:

  • уменьшенное количество анимаций
  • ускоренные transitions
  • возможное отключение части эффектов

Это связано с тем, что touch-интерфейсы чувствительны к задержкам анимаций.

Переопределение глобального поведения

Для полного контроля анимаций можно комбинировать:

  • animate: false
  • кастомные CSS transitions
  • хуки событий
  • динамическое добавление классов

Пример гибридной схемы:

flatpickr("#input", {
  animate: false,
  onOpen: (d, s, i) => {
    requestAnimationFrame(() => {
      i.calendarContainer.classList.add("force-animate");
    });
  }
});
.force-animate {
  transition: opacity 200ms ease, transform 200ms ease;
  opacity: 1;
  transform: translateY(0);
}