Tom Select в мобильной среде ведёт себя принципиально иначе, чем в десктопных браузерах, прежде всего из-за работы виртуальной клавиатуры и динамического изменения viewport. В момент фокусировки на поле ввода браузер изменяет высоту видимой области, пересчитывает геометрию страницы и часто инициирует автоматический скролл к активному элементу. Для компонентов с выпадающими списками это приводит к смещению координат, обрезанию dropdown-меню и потере синхронизации между инпутом и позиционированием панели выбора.
На iOS и Android открытие клавиатуры не является простым overlay-слоем. Система изменяет размеры viewport, что приводит к:
window.innerHeight;resize (не всегда стабильно);visualViewport.height (более надёжный
источник);В контексте Tom Select это критично, поскольку dropdown позиционируется относительно control элемента. При изменении viewport без пересчёта координат список может оказаться:
overflow: hidden;Современный подход к обработке виртуальной клавиатуры основан на
window.visualViewport. Он позволяет отслеживать реальные
изменения видимой области:
visualViewport.height — текущая высота области
просмотра;visualViewport.offsetTop — смещение viewport;resize — изменение размеров при открытии
клавиатуры;scroll — изменение позиции при фиксации
input.В интеграции с Tom Select логика пересчёта позиции dropdown должна
привязываться именно к этим событиям, а не только к
window.resize.
const vv = window.visualViewport;
vv.addEventListener('resize', () => {
tomSelectInstance.refreshOptions(false);
tomSelectInstance.positionDropdown();
});
Такой подход минимизирует рассинхронизацию между реальным положением input и визуальным размещением dropdown.
При активации поля ввода внутри Tom Select происходит цепочка событий:
focus на hidden input или control input;open() или
onFocus);В момент появления виртуальной клавиатуры
getBoundingClientRect() начинает возвращать устаревшие
координаты, если не выполнить повторный расчёт.
Ключевой механизм стабилизации — принудительный reflow позиционирования:
onFocus() {
this.isFocused = true;
requestAnimationFrame(() => {
this.positionDropdown();
});
}
Мобильные браузеры стремятся автоматически прокрутить страницу так, чтобы активный input оказался в центре экрана. Это приводит к конфликту с позиционированием dropdown:
Особенно это заметно в длинных формах, где Tom Select используется внутри scroll-контейнеров.
Решение заключается в комбинировании:
controlInput.addEventListener('focus', (e) => {
e.preventDefault();
});
В некоторых сценариях требуется также временно блокировать scroll страницы:
body.keyboard-open {
overflow: hidden;
}
Виртуальная клавиатура на мобильных устройствах почти всегда использует Input Method Editor (IME), особенно для языков с сложной письменностью. Это влияет на обработку ввода:
keydown не всегда содержит финальный символ;compositionstart,
compositionupdate, compositionend;Tom Select использует внутренний input для поиска и фильтрации опций, поэтому игнорирование composition событий приводит к:
Корректная схема обработки:
let isComposing = false;
input.addEventListener('compositionstart', () => {
isComposing = true;
});
input.addEventListener('compositionend', (e) => {
isComposing = false;
instance.setTextboxValue(e.target.value);
instance.refreshOptions(false);
});
Фильтрация должна выполняться только после завершения composition.
Изменение высоты окна при открытии клавиатуры требует повторного позиционирования dropdown. В противном случае список остаётся на месте, визуально «уезжая» за пределы экрана.
Практическая стратегия:
visualViewport.resize;let lastHeight = window.visualViewport.height;
window.visualViewport.addEventListener('resize', () => {
const currentHeight = window.visualViewport.height;
if (Math.abs(currentHeight - lastHeight) > 50) {
instance.positionDropdown();
}
lastHeight = currentHeight;
});
Пороговое значение позволяет игнорировать микролаги интерфейса.
Если Tom Select находится внутри блока с overflow: auto,
открытие клавиатуры может привести к тому, что контейнер автоматически
прокрутится, скрывая dropdown.
Решение включает:
overscroll-behavior..scroll-container {
overscroll-behavior: contain;
}
Также может применяться сохранение позиции:
const scrollTop = container.scrollTop;
setTimeout(() => {
container.scrollTop = scrollTop;
}, 0);
Закрытие виртуальной клавиатуры не всегда инициирует
blur события. Это приводит к ситуации, когда dropdown
остаётся открытым, хотя пользователь уже не взаимодействует с input.
В таких случаях используется комбинация:
focusout;visibilitychange;document.activeElement.document.addEventListener('visibilitychange', () => {
if (document.hidden) {
instance.close();
}
});
iOS Safari автоматически масштабирует страницу при фокусе на input с font-size меньше 16px. Это вызывает:
Для стабильной работы Tom Select необходимо:
font-size: 16px или выше;<meta name="viewport" content="width=device-width, initial-scale=1">
Стабильная интеграция требует связывания жизненного цикла dropdown с состоянием клавиатуры:
Рекомендуемая модель состояния:
keyboardOpenviewportHeightdropdownOpenЛюбое изменение одного из параметров должно триггерить перерасчёт позиции.
Частые вызовы positionDropdown() могут вызывать layout
thrashing. Особенно при resize событий виртуальной
клавиатуры, которые могут происходить сериями.
Оптимизация включает:
requestAnimationFrame;let ticking = false;
function update() {
if (!ticking) {
requestAnimationFrame(() => {
instance.positionDropdown();
ticking = false;
});
ticking = true;
}
}
window.visualViewport.addEventListener('resize', update);
Такой подход обеспечивает стабильную работу интерфейса даже при частом изменении высоты виртуальной клавиатуры и предотвращает визуальные рассинхронизации между input и выпадающим списком.