Позиционирование на мобильных устройствах

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


Особенности viewport на мобильных устройствах

На мобильных платформах viewport динамичен: при открытии виртуальной клавиатуры высота окна уменьшается, при скролле адресная строка может скрываться, а при смене ориентации происходит мгновенное перерасчёт координат.

Ключевые последствия:

  • изменяется доступная высота экрана без события resize в некоторых браузерах;
  • координаты input-поля пересчитываются с задержкой;
  • фиксированные элементы могут «прыгать» при появлении клавиатуры;
  • position: fixed ведёт себя нестабильно в старых WebView.

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


Базовая модель позиционирования

В стандартной конфигурации календарь рендерится как отдельный DOM-элемент, добавляемый в document.body. Его позиционирование рассчитывается через:

  • координаты input (getBoundingClientRect);
  • текущий scroll offset (window.scrollX, window.scrollY);
  • размеры календаря после рендера;
  • размеры viewport.

Основная формула:

  • top = inputBottom + scrollY
  • left = inputLeft + scrollX

Далее применяется корректировка на переполнение экрана.


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

Одной из ключевых задач является предотвращение ситуации, когда календарь уходит за нижнюю или правую границу.

Типовая логика:

  • если calendarBottom > viewportHeight → показывать календарь сверху input;
  • если calendarRight > viewportWidth → смещать влево;
  • учитывать высоту клавиатуры как дополнительное ограничение viewport.

Поведение можно описать как динамический “flip”:

  • вниз → вверх
  • вверх → вниз
  • вправо → влево (редко, но важно при узких экранах)

Использование режима bound и контейнеров

Pikaday поддерживает режим привязки к контейнеру (bound: true), при котором календарь позиционируется относительно родительского элемента, а не глобального viewport.

Поведение различается:

Без bound:

  • позиционирование относительно document.body;
  • абсолютные координаты страницы;
  • требуется учитывать scroll всей страницы.

С bound:

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

На мобильных устройствах bound-режим особенно важен в следующих сценариях:

  • формы внутри модальных окон;
  • интерфейсы с bottom-sheet компонентами;
  • SPA с виртуальными скролл-контейнерами.

Влияние виртуальной клавиатуры

Открытие клавиатуры — критический момент для позиционирования.

Типовые проблемы:

  • input теряет фокусную область видимости;
  • viewport «сжимается», но DOM координаты input остаются прежними;
  • календарь оказывается перекрыт клавиатурой.

Практическая стратегия:

  • пересчитывать позицию на focus и resize;
  • использовать задержку (debounce) 50–150 мс;
  • учитывать window.visualViewport.height, если доступен.

Особенно важно отслеживать:

  • visualViewport.onresize
  • visualViewport.onscroll

Эти события точнее отражают изменения на мобильных браузерах, чем классический window.resize.


Поведение при скролле страницы

При прокрутке документа календарь должен оставаться «приклеенным» к input.

Используются два подхода:

  1. Перерасчёт позиции на scroll

    • обновление координат при каждом событии scroll;
    • высокая точность;
    • потенциальная нагрузка.
  2. CSS fixed (частично поддерживаемый подход)

    • попытка закрепить календарь относительно viewport;
    • нестабильно на iOS Safari.

В большинстве реализаций Pikaday используется первый подход с оптимизацией через throttle.


Оптимизация частоты перерасчёта

Поскольку scroll и resize могут генерировать десятки событий в секунду, позиционирование должно быть оптимизировано.

Типовые техники:

  • requestAnimationFrame для синхронизации с рендером;
  • throttle (16–50 мс);
  • кеширование размеров календаря после рендера;
  • пересчёт только при изменении input позиции.

Проблемы iOS Safari

iOS Safari создаёт ряд уникальных ограничений:

  • position: fixed может вести себя как absolute при открытой клавиатуре;
  • viewport изменяется визуально, но не всегда через layout viewport;
  • scrollTop может изменяться у body, а не у documentElement;
  • задержка обновления bounding rect.

Следствие: календарь может «уезжать» при вводе текста.

Решения:

  • использование visualViewport;
  • принудительный пересчёт через setTimeout(0);
  • временное отключение анимации позиционирования;
  • фиксация scroll lock на body.

Центрирование относительно input

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

Формула:

  • left = inputLeft + (inputWidth / 2) - (calendarWidth / 2)

При этом добавляются ограничения:

  • не выходить за левую границу viewport;
  • не выходить за правую границу viewport.

Работа с transform и GPU-ускорением

Для уменьшения перерисовки часто применяется:

  • transform: translate3d(...)

Преимущества:

  • меньше layout recalculation;
  • более плавное перемещение при scroll;
  • снижение jitter на слабых устройствах.

Недостатки:

  • смещение координат может не совпадать с DOM bounding box;
  • сложнее отлаживать позиционирование.

Z-index и наложение слоёв

На мобильных интерфейсах календарь часто конфликтует с:

  • модальными окнами;
  • sticky header;
  • bottom navigation;
  • виртуальными клавиатурами.

Pikaday обычно требует:

  • высокий z-index (1000+);
  • изоляцию слоя через отдельный container;
  • отсутствие родительских stacking context, если bound: false.

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

Календарь может изменять размеры при:

  • смене месяца;
  • переключении режима отображения;
  • изменении CSS (responsive breakpoints).

В этих случаях требуется:

  • повторный reposition();
  • пересчёт bounding box;
  • проверка overflow после рендера.

Адаптация к безопасным зонам (safe area)

На устройствах с вырезами и закруглёнными краями экрана учитываются:

  • env(safe-area-inset-top)
  • env(safe-area-inset-bottom)

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


Портальная модель рендера

Рекомендуемая архитектура на мобильных устройствах:

  • календарь рендерится в отдельный DOM-узел;
  • этот узел находится в body;
  • позиционирование полностью управляется JS;
  • отсутствует влияние родительских overflow контейнеров.

Такая модель минимизирует проблемы:

  • обрезания календаря;
  • конфликтов с overflow: hidden;
  • некорректных stacking contexts.

Поведение при смене ориентации

При переходе portrait ↔︎ landscape происходит:

  • резкое изменение viewport width/height;
  • перерасчёт координат input;
  • возможный сдвиг scroll.

Рекомендуемая реакция:

  • слушать orientationchange;
  • вызывать reposition() после завершения анимации браузера;
  • учитывать задержку 200–300 мс для стабильности измерений.

Интерактивное поведение при touch-событиях

На мобильных устройствах взаимодействие отличается от mouse-based:

  • отсутствует hover;
  • tap может сопровождаться scroll;
  • события focus/blur происходят быстрее.

Следствия для позиционирования:

  • календарь должен открываться после завершения touchend;
  • предотвращается «дрожание» позиции при скролле пальцем;
  • обновление координат выполняется после стабилизации касания.

Управление перекрытием input

Частая проблема — календарь перекрывает input, ухудшая UX.

Решения:

  • автоматическое смещение вверх при нехватке места;
  • добавление offset (8–16px);
  • временное поднятие календаря над клавиатурой;
  • адаптивный flip с приоритетом вертикального смещения.

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

Постоянная корректировка осуществляется по следующим триггерам:

  • открытие календаря;
  • изменение даты;
  • scroll;
  • resize;
  • смена ориентации;
  • фокус input;
  • появление/скрытие клавиатуры.

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