Веб

iOS: как заставить Safari менять viewport при фокусе

Почему iOS‑клавиатура в Safari на iPhone не ресайзит layout‑viewport и как это исправить: Visual Viewport API, вычисление --vh, padding‑bottom, scrollIntoView и fallback‑паттерны.

Как заставить клавиатуру Safari на iPhone изменять размер страницы (viewport) при фокусе на поле ввода? В Safari на iOS при активации input размер страницы не ресайзится (нижнее меню остается прилипшим), в отличие от Android, Chrome и Firefox. Как исправить это поведение?

На iPhone в Safari системная ios клавиатура обычно не меняет layout viewport, поэтому элементы с высотой 100vh и fixed‑нижние панели остаются «под» клавиатурой. Решение — комбинировать meta viewport (viewport‑fit=cover), Visual Viewport API + CSS‑переменную --vh и простые обходы: scrollIntoView с задержкой, scroll‑padding/scroll‑margin и сдвиг footer по вычисленной высоте клавиатуры. Ниже — проверенные паттерны, готовый код и fallbackы для старых iOS.


Содержание


Почему клавиатура iOS не ресайзит viewport в Safari на iPhone

Коротко — Safari на iOS разделяет «layout viewport» и «visual viewport». Когда открывается клавиатура, visual viewport (то, что видно) сжимается, но layout viewport остаётся тем же: значения вроде 100vh и поведение position:fixed опираются на layout, поэтому элементы не «поднимаются» вместе с экраном. Так описано в обсуждениях и практических решениях на StackOverflow и в статьях разработчиков (см. пример на StackOverflow: https://stackoverflow.com/questions/39417778/iphone-safari-not-resizing-viewport-on-keyboard-open).

Результат: input может быть видим, но fixed‑панель на bottom остаётся на прежнем месте и оказывается под клавиатурой. Что ещё хуже — разные версии iOS/браузеров ведут себя по‑разному, поэтому нужно универсальное решение.


Быстрое исправление (минимум изменений)

Если нужно скорое улучшение без глубоких правок, сделайте три вещи:

  1. meta viewport с safe‑area:
html
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
  1. базовый CSS, чтобы не полагаться на 100vh напрямую:
css
html { height: -webkit-fill-available; }
:root { --vh: 1vh; }
.app { min-height: calc(var(--vh, 1vh) * 100); } /* вместо height:100vh */
.scroll-container { -webkit-overflow-scrolling: touch; overflow-y: auto; }
  1. простой JS‑fallback: при фокусе прокручиваем input внутрь видимой области с небольшой задержкой:
js
document.addEventListener('focusin', (e) => {
 if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
 setTimeout(() => e.target.scrollIntoView({ block: 'center', behavior: 'smooth' }), 250);
 }
});

Этого часто достаточно: meta+CSS убирают большинство проблем с 100vh, а scrollIntoView гарантирует видимость поля. Подробнее про такие патчи — статья с примерами CSS/JS: https://medium.com/@krutilin.sergey.ks/fixing-the-safari-mobile-resizing-bug-6568f933cde0.


Надёжное решение: Visual Viewport API + CSS‑переменная --vh

Для корректной работы на iOS 13+ используйте Visual Viewport API — она даёт реальную высоту видимой области. Идея: пересчитать «1vh» из visualViewport, хранить в --vh и применять вместо 100vh; одновременно вычислить высоту клавиатуры как разницу между layout (window.innerHeight) и visualViewport.height.

Пример кода:

js
function updateVhAndKeyboard() {
 const vv = window.visualViewport;
 const visibleHeight = vv ? vv.height : window.innerHeight;
 // 1vh в пикселях
 const vh = visibleHeight * 0.01;
 document.documentElement.style.setProperty('--vh', `${vh}px`);

 const keyboardHeight = vv ? Math.max(0, window.innerHeight - vv.height) : 0;
 document.documentElement.style.setProperty('--keyboard-height', `${keyboardHeight}px`);
}

updateVhAndKeyboard();
window.addEventListener('resize', updateVhAndKeyboard);
if (window.visualViewport) window.visualViewport.addEventListener('resize', updateVhAndKeyboard);

CSS:

css
/* full-height блоки */
.fullscreen { height: calc(var(--vh, 1vh) * 100); }

/* на контейнер прокрутки можно добавить паддинг, чтобы не прятать элементы */
.scroll-container { padding-bottom: calc(var(--keyboard-height, 0px) + env(safe-area-inset-bottom)); }

/* сдвиг footer через JS (если хотите чисто через CSS, потребуется записывать переменную в root) */
.bottom-bar { position: fixed; left: 0; right: 0; bottom: 0; transition: transform .18s ease; }

Вместо постоянного reliance на 100vh вы теперь подстраиваетесь под реальную видимую область. Пример и обоснование подхода подробно рассмотрены в блоге Брэма (Visual Viewport и VirtualKeyboard): https://www.bram.us/2021/09/13/prevent-items-from-being-hidden-underneath-the-virtual-keyboard-by-means-of-the-virtualkeyboard-api/.


Паттерны для fixed‑элементов и форм (реальные примеры)

Ниже — практические варианты, выбирайте в зависимости от UX и сложности проекта.

  1. Переключать fixed → absolute внутри прокручиваемого контейнера
  • Помещайте footer в контейнер с position: relative; при фокусе делайте footer position: absolute; bottom: var(–keyboard-height); — тогда он окажется над клавиатурой.
  1. Сдвигать фиксированный footer через transform
  • Вычисляете keyboardHeight (см. выше) и ставите footer.style.transform = translateY(-${keyboardHeight}px); это плавно поднимет панель.
  1. scroll-padding/scroll-margin для корректных scrollIntoView
css
.scroll-container { scroll-padding-bottom: calc(var(--keyboard-height, 0px) + 20px); }
input, textarea { scroll-margin-bottom: 20px; }

Это помогает браузеру корректно позиционировать поле при программной прокрутке.

  1. Избегайте body { overflow: hidden } и жестких max‑height: это мешает прокрутке и работе visualViewport.

  2. Паддинг для форм: если динамически добавлять padding‑bottom равный keyboardHeight — поля не будут скрываться.

Пример общего подхода:

js
function onFocusAdjust(e) {
 const kb = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--keyboard-height')) || 0;
 const sc = document.querySelector('.scroll-container');
 sc.style.paddingBottom = `${kb}px`;
 setTimeout(() => e.target.scrollIntoView({ block: 'center' }), 200);
}
document.addEventListener('focusin', onFocusAdjust);

Fallbackы и кроссбраузерность (VirtualKeyboard API и старые iOS)

VirtualKeyboard API (в современных Chromium) предоставляет события/метрики клавиатуры и облегчит задачу, но Safari пока не реализовал этот API полностью. Поэтому делаем feature‑detection и fallback:

  • Если есть navigator.virtualKeyboard → используйте его (в Chrome/Android) для чтения размеров/geometry.
  • Иначе — используйте window.visualViewport (iOS 13+).
  • Для старых iOS без visualViewport используйте простой focusin + setTimeout + scrollIntoView или фиксированную подгонку (например, добавлять padding‑bottom ~300px) — это грязно, но работает в большинстве случаев.

Пример проверки:

js
if ('virtualKeyboard' in navigator) {
 // handle with VirtualKeyboard API where available
} else if (window.visualViewport) {
 // visualViewport approach
} else {
 // fallback: timeout + scrollIntoView
}

Для русскоязычных заметок о таких обходах см. обсуждение на Хабре: https://qna.habr.com/q/522091 и старые ветки StackOverflow (https://stackoverflow.com/questions/23940438/want-to-have-browser-viewport-resize-when-ios-keyboard-is-activated).


Тестирование и отладка на iPhone

Как отлаживать? Несколько советов:

  • Тестируйте на реальных устройствах. Эмуляторы часто ведут себя иначе.
  • Используйте Safari Web Inspector (Mac): Develop → [iPhone] → Inspect. Вы увидите window.visualViewport и window.innerHeight в реальном времени.
  • Логируйте значения при focusin/focusout:
js
document.addEventListener('focusin', () => {
 console.log('innerHeight', window.innerHeight, 'visual', window.visualViewport && window.visualViewport.height);
});
  • Пробуйте разные сценарии: с адресной строкой видимой/скрытой, в портрете и ландшафте, с внешней клавиатурой (она меняет поведение).
  • Помните: тайминги открытия клавиатуры могут варьироваться — экспериментируйте с задержкой 200–400 ms для scrollIntoView.

Небольшая проверка: если при открытии клавиатуры window.innerHeight не меняется, а window.visualViewport.height уменьшается — ваш браузер ведёт себя как Safari и нуждается в visualViewport/–vh патче.


Источники

  1. iPhone Safari not resizing viewport on keyboard open — Stack Overflow
  2. Fixing the Safari Mobile Resizing Bug: A Developer’s Guide — Medium
  3. Prevent items from being hidden underneath the Virtual Keyboard — Bram.us
  4. Want to have browser viewport resize when iOS keyboard is activated — Stack Overflow
  5. Как узнать высоту клавиатуры и нижнего бара на iOS Safari? — Habr Q&A

Заключение

Коротко: Safari на iPhone не меняет layout viewport — поэтому нужно имитировать ресайз через Visual Viewport API + CSS‑переменную --vh и подстраивать fixed‑элементы (или временно переключать их поведение). Для старых устройств ставьте fallback: scrollIntoView с задержкой и добавление padding‑bottom. С этим набором приёмов поведение ios клавиатуры в Safari станет предсказуемым и пользовательский интерфейс — удобным.

Авторы
Проверено модерацией
Модерация