Как верстать сайты под iOS: гайд для фронтенд-разработчиков с примерами кода

Вёрстка под iOS — это не просто адаптация под маленькие экраны, а учёт десятков нюансов: от особенностей рендеринга в Safari до специфических жестов пользователей. Даже идеально сверстанный сайт на Android может «сломаться» на iPhone из-за неучтённых мелочей: неправильного масштабирования при повороте экрана, «прыгающего» контента при появлении панелей навигации или игнорирования системных шрифтов. В этой статье разберём ключевые отличия вёрстки под iOS, которые игнорируют 90% разработчиков, но из-за которых теряется до 30% трафика с устройств Apple.

По данным StatCounter (2026), доля iOS среди мобильных устройств в России и СНГ составляет ~25%, а в США и Европе — до 50%. При этом 68% пользователей iPhone покидают сайт, если он некорректно отображается или работает с задержками. Мы не будем перечислять базовые принципы адаптивной вёрстки (вроде медиа-запросов) — сфокусируемся на уникальных «яблочных» лайфхаках, которые не описаны в стандартных туториалах. От корректной настройки viewport до обработки свайпов и работы с WebKit-префиксами.

1. Viewport для iOS: почему meta-тег с width=device-width не всегда работает

Базовый тег <meta name="viewport" content="width=device-width, initial-scale=1"> на iOS ведёт себя иначе, чем на Android. Главная проблема — автоматическое масштабирование при повороте экрана. Safari может игнорировать initial-scale=1 и увеличивать контент, если ширина вёрстки превышает 980px (даже на iPhone 15 Pro Max!). Решение:

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">

Разберём параметры:

  • 🔍 maximum-scale=1 — блокирует зум при повороте (иначе Safari может увеличить контент на 120–150%).
  • 🚫 user-scalable=no — отключает жесты масштабирования (актуально для веб-приложений). Не используйте это на контентных сайтах — Apple может отклонить такое приложение в App Store.
  • 📱 viewport-fit=cover — растягивает вёрстку на весь экран, включая область под «чёлкой» (safe area) на iPhone X и новее.
⚠️ Внимание: Если ваш сайт должен поддерживать iPad в режиме split-view (две программы одновременно), добавьте shrink-to-fit=no. Иначе Safari будет принудительно уменьшать масштаб.
📊 Какой инструмент вы используете для тестирования вёрстки на iOS?
Реальное устройство (iPhone/iPad)
Эмулятор в Xcode
BrowserStack
Chrome DevTools (Device Mode)
Другой

2. Safe Area: как не перекрыть контент «чёлкой» и жестами

Начиная с iPhone X (2017), все устройства Apple имеют вырез («чёлку») и жестовые зоны внизу экрана. Если не учесть safe area, кнопки или текст могут оказаться под системными элементами. Например, фиксированное меню внизу экрана перекроет свайп для возврата назад.

Решение — использовать CSS-переменные Apple:

:root {

--safe-area-inset-top: env(safe-area-inset-top);

--safe-area-inset-bottom: env(safe-area-inset-bottom);

}

body {

padding:

max(10px, var(--safe-area-inset-top)) / Отступ сверху /

0

max(10px, var(--safe-area-inset-bottom)); / Отступ снизу /

}

Для фиксированных элементов (например, нижней панели навигации):

.bottom-nav {

padding-bottom: max(10px, env(safe-area-inset-bottom));

background: white;

position: fixed;

bottom: 0;

left: 0;

right: 0;

}

Устройство safe-area-inset-top (портрет) safe-area-inset-bottom (портрет)
iPhone 15/14/13/12 47px 34px
iPhone 11/XR/XS Max 44px 34px
iPad Pro (2022+) 24px 20px
iPad Mini (2021) 0px 20px
⚠️ Внимание: На iPad в ландшафтном режиме safe-area-inset-left и safe-area-inset-right могут быть ненулевыми из-за док-станции (если подключена клавиатура). Всегда тестируйте вёрстку в обоих ориентациях!

Учтён отступ сверху (safe-area-inset-top) для «чёлки»

Добавлен отступ снизу (safe-area-inset-bottom) для жестовой панели

Фиксированные элементы (модалки, тулбары) не перекрывают safe area

Тестирование проведено на iPhone и iPad в портретном/ландшафтном режимах-->

3. Шрифты на iOS: почему -apple-system не достаточно

Стандартная рекомендация — использовать системный шрифт Apple:

font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;

Но этого мало. Проблемы, с которыми столкнётесь:

  • 🔤 Несовпадение высоты строк: на iOS line-height: 1.5 может давать визуально разный результат из-за особенностей рендеринга San Francisco.
  • 📏 Отсутствие полужирного начертания: если не указать font-weight: 600, текст может отображаться тоньше, чем на других платформах.
  • 🌐 Проблемы с кириллицей: шрифт San Francisco не оптимизирован для русского языка — буквы «ж», «щ» или «ъ» могут выглядеть размыто.

Решение:

body {

font-family:

-apple-system, / iOS/macOS /

BlinkMacSystemFont, / Chrome на Mac /

"Segoe UI", / Windows /

"Roboto", / Android /

"Helvetica Neue", / Старые iOS /

sans-serif;

font-weight: 400;

line-height: 1.6; / Оптимально для iOS /

-webkit-font-smoothing: antialiased; / Сглаживание для Safari /

-moz-osx-font-smoothing: grayscale;

}

/ Для заголовков /

h1, h2, h3 {

font-weight: 600; / Semi-bold для iOS /

letter-spacing: -0.02em; / Улучшает читаемость /

}

4. Жесты и тачи: обработка свайпов, тапов и 3D Touch

На iOS пользователи ожидают специфические жесты:

  • 👆 Свайп справа налево — возврат назад (аналог кнопки «Назад» в браузере).
  • 👇 Свайп вниз от верхнего края — обновление страницы (pull-to-refresh).
  • 🖱️ 3D Touch / Haptic Touch — контекстное меню при сильном нажатии.

Чтобы не ломать пользовательский опыт:

  • 🚫 Не блокируйте свайп назад с помощью overscroll-behavior или touch-action: none. Safari может игнорировать это, но пользователи будут раздражены.
  • Добавьте pull-to-refresh для одностраничных приложений (SPA):
// Пример для React (используем библиотеку react-pull-to-refresh)

import PullToRefresh from 'react-pull-to-refresh';

function App() {

return (

<PullToRefresh

onRefresh={async () => await window.location.reload()}

className="pull-to-refresh-iOS"

>

{/ Ваш контент /}

</PullToRefresh>

);

}

Для обработки 3D Touch (на iPhone 6s–11) или Haptic Touch (на новых моделях):

// Проверка поддержки 3D Touch

if ('force' in window) {

document.addEventListener('touchforcechange', (e) => {

if (e.touches[0].force > 0.5) {

console.log('Сильное нажатие (3D Touch)');

// Открываем контекстное меню

}

});

}

Как отладить жесты на симуляторе Xcode

1. Запустите симулятор iPhone в Xcode.

2. В меню симулятора выберите Features → Force Touch (для старых моделей) или Features → Haptic Touch (для новых).

3. Используйте клавиши Option + Shift + клик для имитации сильного нажатия.

4. Для свайпов используйте трекпад с нажатой кнопкой (или тачбар на MacBook Pro).

5. Анимации и переходы: почему на iOS они «лагают»

Safari использует другой рендеринг-движок (WebKit), чем Chrome (Blink). Из-за этого анимации на transform и opacity могут тормозить, даже если на Android всё плавно. Правила оптимизации:

  • Используйте только transform и opacity для анимаций. Избегайте width, height, margin — они триггерят дорогой layout.
  • 🎨 Включите аппаратное ускорение:
.animated-element {

transform: translateZ(0); / Триггерит GPU-ускорение /

will-change: transform, opacity; / Подсказка браузеру /

backface-visibility: hidden; / Фикс для мерцания на iOS /

}

Пример плавной анимации для модального окна:

@keyframes fadeIn {

from { opacity: 0; transform: translateY(20px); }

to { opacity: 1; transform: translateY(0); }

}

.modal {

animation: fadeIn 0.3s ease-out forwards;

/ Для iOS добавляем: /

-webkit-animation: fadeIn 0.3s ease-out forwards;

-webkit-backface-visibility: hidden;

}

Для сложных анимаций (например, паралакс-эффектов) используйте библиотеки, оптимизированные под iOS:

  • 📦 GSAP — лучший выбор для высокопроизводительных анимаций.
  • 🎭 Framer Motion — если работаете с React.
⚠️ Внимание: На iPad с iPadOS 16+ анимации могут тормозить в режиме Stage Manager (многозадачность). Тестируйте производительность в этом режиме!

6. Формы и инпуты: автозаполнение, клавиатура и валидация

На iOS формы ведут себя иначе:

  • 🔑 Автозаполнение: Safari агрессивно предлагает сохранённые пароли, даже если вы отключили autocomplete.
  • ⌨️ Клавиатура: может закрывать инпуты, если не настроено правильное позиционирование.
  • 📱 Типы инпутов: type="date" отображает нативный пикер, но он выглядит по-разному на iPhone и iPad.

Решения:

  1. Отключение автозаполнения (если нужно):
<input type="text" autocomplete="new-password" />

<!-- или -->

<input type="text" autocomplete="off" readonly onfocus="this.removeAttribute('readonly')">

  1. Фикс для клавиатуры, перекрывающей инпуты:
/ Сдвигаем вьюпорт при фокусе на инпут /

input, textarea {

font-size: 16px; / Минимальный размер для iOS /

}

@media (max-width: 767px) {

.form-container {

padding-bottom: 120px; / Место для клавиатуры /

}

}

Для кастомных селектов (например, с библиотекой Select2) добавьте:

/ Фикс для нативного скролла в селектах на iOS /

.select2-container {

-webkit-overflow-scrolling: touch;

}

7. Тестирование и отладка: инструменты для iOS

Тестировать вёрстку на iOS сложнее, чем на Android, из-за закрытости экосистемы Apple. Основные инструменты:

Инструмент Для чего нужен Особенности
Xcode Simulator Эмуляция всех устройств Apple Требует Mac, не показывает реальную производительность
BrowserStack Тестирование на реальных устройствах Платный, но поддерживает все версии iOS
Safari Web Inspector Отладка через USB-подключение Работает только на Mac, требует включения в настройках iPhone
LambdaTest Кроссбраузерное тестирование Бесплатный тариф с ограничениями

Как подключить Safari Web Inspector:

  1. На iPhone: перейдите в Настройки → Safari → Дополнительно → Веб-инспектор и включите его.
  2. Подключите iPhone к Mac по USB.
  3. Откройте Safari на Mac, в меню выберите Разработка → [Ваш iPhone] → [Ваш сайт].

Для проверки производительности используйте:

// В консоли Safari Web Inspector

console.profile('Анимация');

// Ваш код

console.profileEnd('Анимация');

⚠️ Внимание: На iOS 17+ Safari может кэшировать агрессивнее, чем раньше. Чтобы обновить страницу без кэша, используйте комбинацию: нажмите и удерживайте кнопку обновления → выберите «Перезагрузить без кэша».

FAQ: Частые вопросы по вёрстке под iOS

Почему на iPhone сайт выглядит размыто, хотя на Android всё чёткое?

Проблема в некорректном viewport или отсутствии поддержки Retina-дисплеев. Проверьте:

  1. Есть ли тег <meta name="viewport"> с width=device-width.
  2. Используются ли векторы (SVG) или изображения в 2x/3x разрешении (например, image@2x.png).
  3. Не применён ли к тексту -webkit-text-size-adjust: none — это может сделать шрифты размытыми.

Решение: добавьте для изображений:

<img src="image.png" srcset="image@2x.png 2x, image@3x.png 3x">
Как отключить зум на iOS, но оставить возможность масштабирования для пользователей с плохим зрением?

Используйте maximum-scale=1, но добавьте альтернативный способ увеличения контента:

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5">

Или реализуйте кастомный зум через JavaScript с кнопками «+»/«–» (например, как на сайтах банков).

Почему на iPad сайт отображается как на десктопе, а не как мобильная версия?

Safari на iPad по умолчанию отправляет user-agent десктопного браузера. Чтобы принудительно показать мобильную версию:

  1. Добавьте в viewport параметр shrink-to-fit=no.
  2. Используйте CSS-медиазапросы с max-width: 1024px (максимальная ширина iPad в портретном режиме).
  3. Для JavaScript-проверки используйте:
if (/iPad/.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)) {

// Это iPad — применяем мобильные стили

document.body.classList.add('is-ipad');

}

Как сделать, чтобы видео на iOS автовоспроизводилось без звука?

На iOS автовоспроизведение видео блокируется, если:

  • У видео есть звук (даже если muted).
  • Видео не в поле зрения (playsinline не указан).
  • Пользователь не взаимодействовал со страницей.

Решение:

<video playsinline muted autoplay loop>

<source src="video.mp4" type="video/mp4">

</video>

Для фоновых видео используйте:

video {

position: fixed;

top: 0;

left: 0;

width: 100%;

height: 100%;

object-fit: cover;

z-index: -1;

}

Почему на iOS не работает position: fixed?

Проблема в том, что Mobile Safari эмулирует position: fixed через JavaScript, а не нативно. Это приводит к:

  • Дёрганью при скролле.
  • Неправильной работе с клавиатурой.
  • Багу с прокруткой внутри фиксированных блоков.

Решения:

  1. Замените fixed на sticky (если подходит по логике).
  2. Используйте библиотеку ios-fixed-jank-fix.
  3. Для модальных окон применяйте position: absolute с JavaScript-позиционированием.