Safari & Chrome Mobile Keyboard Fix
Комплексный фикс для печально известного бага виртуальной клавиатуры, который затрагивает все веб-чаты (Telegram Web, WhatsApp Web и др.) в Safari, и проблемы viewport в Chrome Mobile. Вдохновлен UX решением Яндекс Мессенджера.
Проблема
Проблема Safari
Когда пользователи печатают в input чата на iOS Safari:
- Открывается виртуальная клавиатура
- Пользователь скроллит страницу (не чат)
- Input остается в фокусе с открытой клавиатурой
- Контент страницы становится недоступным за клавиатурой
- Нет встроенного способа закрыть клавиатуру
Проблема Chrome Mobile
Виртуальная клавиатура изменяет размер viewport, ломая 100vh layouts в интерфейсах чатов.
Решение
Двухчастный фикс:
- Safari: Определяет скролл страницы при открытой клавиатуре → авто-снятие фокуса
- Chrome: Использует meta тег
interactive-widget=resizes-content
Установка
1. Фикс Chrome/Android (Meta тег)
Добавить в <head>:
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no interactive-widget=resizes-content"/>2. Фикс Safari/iOS (JavaScript)
Импортировать и запустить глобально в главном скрипте:
import './safari-keyboard-viewport-fix.js'Или подключить напрямую:
<script src="safari-keyboard-viewport-fix.js"></script>Как работает фикс Safari
- Обнаружение: Мониторит события
visualViewport.resize - Состояние клавиатуры: Отслеживает фокус на INPUT/TEXTAREA
- Определение скролла: Сравнивает изменения
offsetTop(с throttling) - Авто-blur: Закрывает клавиатуру если страница скроллится при открытой клавиатуре
Это имитирует поведение Яндекс Мессенджера - единственного крупного чат-приложения, которое решило это корректно.
Исходный код
chrome-keyboard-fix.html (Meta тег)
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no interactive-widget=resizes-content"/>safari-keyboard-viewport-fix.js (Полное решение)
document.addEventListener('DOMContentLoaded', () => { if (isSafari() && window.visualViewport) { let { getValue: getOffsetTop, setValue: setOffsetTop } = useThrottledValue( window.visualViewport.offsetTop, 100, ) let keyboardIsOpen = false
const hideKeyboardOnScrollWindow = (e) => { const windowIsScrolled = window.visualViewport.offsetTop !== getOffsetTop()
if (keyboardIsOpen && windowIsScrolled) { document.activeElement.blur() } }
window.visualViewport.addEventListener('resize', (e) => { const isInputFocused = ['INPUT', 'TEXTAREA'].includes(document.activeElement?.tagName)
if (isInputFocused && !keyboardIsOpen) { keyboardIsOpen = true window.addEventListener('touchmove', hideKeyboardOnScrollWindow) window.addEventListener('scroll', hideKeyboardOnScrollWindow) } if (!isInputFocused) { keyboardIsOpen = false window.removeEventListener('touchmove', hideKeyboardOnScrollWindow) window.removeEventListener('scroll', hideKeyboardOnScrollWindow) } setOffsetTop(window.visualViewport.offsetTop) }) }
function isSafari() { const userAgent = navigator.userAgent return userAgent.includes('Safari') && !userAgent.includes('Chrome') }
function useThrottledValue(initialValue, delay) { let rawValue = initialValue let throttledValue = initialValue let timeoutId = null
const updateThrottledValue = () => { throttledValue = rawValue }
return { setValue: function (newValue) { rawValue = newValue if (!timeoutId) { updateThrottledValue() timeoutId = setTimeout(() => { timeoutId = null }, delay) } }, getValue: function () { return throttledValue }, } }})Почему это важно
- ✅ Исправляет Telegram Web, WhatsApp Web, Discord Web в Safari
- ✅ Предотвращает сломанный UX с застрявшими клавиатурами
- ✅ Восстанавливает нормальное поведение скролла чата
- ✅ Без зависимостей, ванильный JavaScript
- ✅ Проверенное решение (4+ часа исследований)
Затронутые платформы
- Safari iOS: ❌ Сломано (исправлено этим скриптом)
- Chrome Android: ❌ Проблемы viewport (исправлены meta тегом)
- Яндекс Мессенджер: ✅ Уже имеет встроенный фикс
Технические детали
Время исследования: ~4 часа
Вдохновение: UX реализация Яндекс Мессенджера
Сложность: Обрабатывает edge cases visualViewport API
Производительность: Throttling до 100ms для плавного скролла