Files
wow-design-wiki/mobile/mobile-improvements-case-study.md
T

14 KiB
Raw Blame History

Кейс-стади: Мобильная оптимизация лендинга Никиты Тарасова

Production-кейс: лендинг ~2300 строк, 100KB, одиночный HTML-файл → улучшенная мобильная версия. 20+ мобильных улучшений.

Исходные данные

  • Тип: Персональный лендинг (один HTML-файл, без сборщика)
  • Объём: ~2325 строк, ~101KB
  • Стек: Чистый HTML + CSS + минимальный vanilla JS, Tailwind CDN
  • Проблема: Базовые @media (max-width: 768px) медиа-запросы, нет safe-area, нет sticky CTA, нет prefers-reduced-motion, нет touch-оптимизаций

Категории улучшений

1. 🏠 Viewport & Safe Area (iOS)

Проблема: 100vh обрезает контент на iOS

На iPhone Safari 100vh включает область адресной строки, которая при скролле сжимается. Контент внизу обрезается.

Исправление:

<!-- Было -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<!-- Стало -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
/* Для секции hero */
#hero {
    min-height: 100dvh; /* dynamic viewport height — учитывает/не учитывает UI браузера */
}

/* Для контейнера — safe-area */
@supports (padding: max(0px)) {
    .container {
        padding-left: max(24px, env(safe-area-inset-left));
        padding-right: max(24px, env(safe-area-inset-right));
    }
    body {
        padding-bottom: max(0px, env(safe-area-inset-bottom));
    }
}

/* Для фиксированной навигации */
.nav {
    padding-top: max(0px, env(safe-area-inset-top));
}

Зачем: Без viewport-fit=cover safe-area инсеты игнорируются. Без dvh на iPhone 15 Pro с Dynamic Island hero-секция обрезается на ~50px.


2. 📱 Sticky Bottom CTA (мобильная конверсия)

Проблема: на мобильном CTA «внизу» скрыты в конце длинной страницы. Пользователь должен пролистать обратно.

Исправление:

.sticky-cta {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    padding: 12px 16px;
    padding-bottom: max(12px, env(safe-area-inset-bottom));
    background: rgba(10, 10, 11, 0.95);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    border-top: 1px solid var(--border);
    z-index: 90;
    display: none; /* desktop: hidden */
    flex-direction: column;
    gap: 4px;
    box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.3);
}

/* Показываем только на мобильных */
@media (max-width: 768px) {
    .sticky-cta { display: flex; }
    main { padding-bottom: 80px; } /* место под sticky CTA */
}
<div class="sticky-cta">
    <a href="#contact" class="btn btn-primary sticky-cta__btn">Обсудить проект</a>
    <span class="sticky-cta__hint">Средний отклик — менее 2 часов</span>
</div>

Зачем: Sticky CTA на мобильном повышает конверсию на 15-30% (источники: Baymard Institute, CXL). Пользователь всегда видит кнопку действия, не нужно скроллить.


3. 👆 Touch Targets (WCAG 2.5.5)

Проблема: кнопки мобильного меню и ссылки имеют произвольный размер. На телефоне с пальцем трудно попасть.

Исправление:

:root {
    --touch-min: 44px; /* минимум по WCAG 2.5.5 и Apple HIG */
}

.mobile-menu-btn {
    min-width: var(--touch-min);
    min-height: var(--touch-min);
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.mobile-menu a {
    min-height: var(--touch-min);
    transition: color 0.2s;
}
.mobile-menu a:active {
    background: var(--accent-dim);
}

Зачем: Apple HIG требует 44×44pt, WCAG 2.5.5 — 44×44px. Меньше — палец не попадает, пользователь уходит.


4. 🎯 Touch Feedback

Проблема: на десктопе hover-эффекты, на мобильном — ничего. Пользователь не понимает, что нажал.

Исправление:

.btn {
    /* Отключаем тёмный фон при тапе на Chrome/Android */
    -webkit-tap-highlight-color: transparent;
    will-change: transform;
}

/* Active state для мобильных */
.btn:active {
    transform: scale(0.97); /* микро-сжатие как в нативных приложениях */
}

.sticky-cta__btn:active {
    transform: scale(0.98);
}

Зачем: Визуальная обратная связь при тапе критична на тач-устройствах. Scale 0.97 создаёт ощущение «нажатия» без задержки.


5. 📦 Font Loading (FOIT/FOUT)

Проблема: Google Fonts загружается асинхронно, текст появляется сначала system font, потом Inter → видимый «скачок» шрифта.

Исправление:

<!-- Preload CSS-ресурса -->
<link rel="preload" href="https://fonts.googleapis.com/css2/..." as="style" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2/..." />

Зачем: preload сообщает браузеру приоритет загрузки шрифта. Без него шрифт может загрузиться через 2-3 секунды.


6. ♿ Accessibility

Предпочтение reduced motion

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
    .reveal { opacity: 1; transform: none; }
}

Зачем: ~15% пользователей имеют включённый reduced motion (вестибулярные нарушения, мигрень). Игнорирование prefers-reduced-motion — нарушение WCAG 2.2.1.

iOS Bounce (overscroll-behavior)

body {
    overscroll-behavior-y: contain;
}
.mobile-menu {
    overflow-y: auto;
    overscroll-behavior-y: contain;
}

Зачем: На iOS Safari overscroll-behavior по умолчанию auto создаёт пружинящий эффект, который ломает ощущение «приложения». contain ограничивает bounce внутри элемента.

Backdrop-filter для всех браузеров

backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px); /* Safari 9-15 */

Зачем: -webkit- префикс нужен для Safari на iOS 15 и младше. Без него blur не работает.


7. 📐 Layout on Mobile

Trust Bar 4-колонка → 2×2

@media (max-width: 768px) {
    .trust-grid {
        grid-template-columns: repeat(2, 1fr) !important;
    }
}

Hero-секция: 100dvh вместо 100vh

@media (max-width: 768px) {
    #hero {
        min-height: 100dvh;
    }
}

Фото hero на мобильном — ограниченный размер

@media (max-width: 768px) {
    .photo-hero-mobile {
        max-height: 280px;
        aspect-ratio: 4/5;
    }
}

Зачем: Полноразмерное фото 600×900 на мобильном занимает весь экран. Ограничение до 280px оставляет место для CTA над фото.

Контейнер контактов: CTA вертикально

@media (max-width: 768px) {
    .cta-stack {
        flex-direction: column !important;
    }
    .cta-stack .btn {
        width: 100%;
    }
}

8. 🍔 Мобильное меню

Close button внутри

<button class="mobile-menu-close" aria-label="Закрыть меню" onclick="...">✕</button>

Emoji иконки для навигации

<a href="#services">📊 Услуги</a>
<a href="#products">📦 Продукты</a>
<a href="#cases">🏆 Кейсы</a>
<a href="#about">👤 Обо мне</a>

Зачем: На мобильном нет hover. Emoji даёт визуальный якорь. Close button в правом верхнем углу — стандартный паттерн iOS.


Сводная таблица всех изменений

# Категория Изменение Приоритет Impact
1 Viewport viewport-fit=cover 🔴 Critical iOS safe-area
2 Viewport 100dvh для hero 🔴 Critical iOS контент
3 Layout env(safe-area-inset-*) 🔴 Critical iPhone notch/Dynamic Island
4 CTA Sticky bottom CTA 🟡 High +15-30% конверсия
5 Touch 44×44px минимум 🟡 High WCAG 2.5.5
6 Touch :active feedback 🟡 High Пользовательский опыт
7 Touch -webkit-tap-highlight-color 🟡 High UX на Android
8 Perf Font preload 🟡 High Убирает FOIT
9 A11y prefers-reduced-motion 🟢 Medium 15% пользователей
10 A11y overscroll-behavior-y 🟢 Medium iOS bounce
11 A11y -webkit-backdrop-filter 🟢 Medium Safari iOS
12 Layout Hero photo limit 🟢 Medium Компоновка
13 Layout Trust bar 2×2 🟢 Medium Компактность
14 Layout CTA vertical stack 🟢 Medium Touch-удобство
15 Layout Container safe padding 🔴 Critical Контент не обрезан
16 Layout main bottom padding 🟡 High Sticky CTA не перекрывает
17 Nav Close button mobile 🟡 High Мобильное меню
18 Nav Emoji icons 🟢 Low Мобильная навигация
19 Nav padding-top safe-area 🟡 High Навигация не обрезана
20 Nav overflow-y: auto menu 🟢 Medium Scroll внутри меню

Антипаттерны, которых стоит избегать

❌ 100vh вместо 100dvh

/* БУДЬТЕ ОСТОРОЖНЫ — iOS Safari */
.hero { min-height: 100vh; } /* ← контент обрезан */
.hero { min-height: 100dvh; } /* ← правильно */

❌ Фиксированная позиция без safe-area

/* БУДЬТЕ ОСТОРОЖНЫ — iPhone X+ */
.footer { bottom: 0; } /* ← под home indicator */
.footer { bottom: env(safe-area-inset-bottom); } /* ← правильно */

❌ Touch-элементы меньше 44px

/* БУДЬТЕ ОСТОРОЖНЫ */
.icon-btn { width: 32px; height: 32px; } /* ← палец не попадёт */
.icon-btn { min-width: 44px; min-height: 44px; } /* ← правильно */

❌ Скрытие hover вместо адаптации

/* БУДЬТЕ ОСТОРОЖНЫ */
.btn:hover { background: gold; } /* ← на тач-экранах не работает */
.btn:active { background: gold; } /* ← добавьте для тач */

❌ Фиксированные размеры для шрифтов

/* БУДЬТЕ ОСТОРОЖНЫ */
h1 { font-size: 2.5rem; } /* ← одинаковый на всех экранах */
h1 { font-size: clamp(2rem, 6vw, 3.5rem); } /* ← адаптивный */

Чек-лист тестирования

Обязательные тесты:

  • iPhone 15 Pro (Dynamic Island) — Safari
  • iPhone SE (маленький экран) — Safari
  • Android (Pixel) — Chrome
  • iPad — Safari
  • Landscape — Safari, Chrome
  • Reduced motion — Settings > Accessibility
  • Dark mode — системные настройки

Инструменты:

  1. Chrome DevTools — Device Toolbar (Ctrl+Shift+M)
  2. Safari Web Inspector — Remote debugging на iPhone
  3. Lighthouse — Performance, Accessibility, Mobile-friendly
  4. WebPageTest — реальные тесты с реальных устройств
  5. BrowserStack — реальные устройства, не только эмуляция

Метрики:

  • LCP (Largest Contentful Paint) < 2.5s на мобильном
  • INP (Interaction to Next Paint) < 200ms
  • CLS (Cumulative Layout Shift) < 0.1
  • Touch targets ≥ 44×44px
  • Font loading < 1s (FOIT невидимый)

Итог

20 изменений, которые превратили десктопный лендинг в полноценное мобильное приложение:

  1. Safe area — контент не обрезан на iPhone с notch/Dynamic Island
  2. Sticky CTA — кнопка действия всегда видна → рост конверсии
  3. Touch targets — 44px минимум по стандартам Apple/Google
  4. Touch feedback — :active scale вместо ненавидимого hover
  5. dvh — hero занимает весь экран без обрезки
  6. Accessibility — reduced-motion, overscroll, WCAG
  7. Performance — preload шрифтов

Общий размер файла: +8KB (было 101KB, стало 109KB)


Источники