Что мобильный трафик в каталогах давно перевалил за половину, знают все. Но проверяют мобильную версию обычно на своём телефоне — а он у разработчика новый, с шириной экрана 390 или 430 пикселей, и на нём всё в порядке. Между тем есть ширина, на которой каталог рассыпается, и посетителей с такой шириной больше, чем кажется. Разберём, что именно ломается на узких экранах и в каком порядке это чинить.
Почему 320, если таких телефонов почти нет
320 CSS-пикселей — это ширина iPhone SE первого поколения и старых Android. Доля таких устройств в статистике действительно мизерная, и на этом обычно разговор заканчивается. Но 320 — это не про конкретный телефон, а про нижнюю границу, на которой вёрстка обязана держаться.
- Galaxy Fold в сложенном виде — 280 пикселей, то есть ещё уже
- Окно браузера, растянутое на треть экрана планшета в режиме нескольких окон
- Увеличенный системный шрифт в iOS: эффективная ширина в CSS-пикселях уменьшается
- Встроенные webview внутри приложений — из Telegram, VK, почтовых клиентов
- Альбомная ориентация, где сжимается уже не ширина, а высота — и ломается всё, что привязано к 100vh
Практическое правило простое: если каталог держится на 320, он держится везде. Если не держится — вы не знаете, у какой части посетителей он выглядит так же, потому что в Метрике есть разрешение экрана, но нет факта горизонтального скролла. Люди просто уходят, и в отчётах это выглядит как плохой трафик.
Горизонтальный скролл — первая и главная поломка
Симптом узнаётся сразу: страницу можно потянуть вбок, справа появляется пустая полоса, липкая шапка съезжает вместе с содержимым. Причина всегда одна и та же — какой-то элемент шире контейнера. Лечится это не overflow-x: hidden на body: так вы заклеиваете индикатор, а элемент остаётся широким и продолжает ломать раскладку изнутри.
Виновник находится одной строкой в консоли браузера: перебрать все элементы и вывести те, чья правая граница выходит за ширину документа — document.querySelectorAll('*') и сравнение getBoundingClientRect().right с documentElement.clientWidth. Дальше по списку: почти всегда это два-три элемента, а не двадцать.
- Длинные неразрывные строки: артикулы, коды, номера вида ABC-12345678-XYZ. Лечится <code>overflow-wrap: anywhere</code> на конкретной ячейке, а не на всей странице
- Таблица характеристик с заданными в пикселях ширинами колонок
- Унаследованный от десктопной сетки <code>min-width</code> на карточке товара
- Абсолютно позиционированный бейдж со скидкой, выходящий за пределы карточки
- Слайдер, который считает ширину в JS и промахивается на первом кадре, до полной загрузки шрифтов
- Внутренние отступы в vw вместе с <code>width: 100%</code> — сумма оказывается больше экрана
Таблица характеристик
На карточке товара это самый частый источник скролла. Правильное решение — не сжимать таблицу до нечитаемости, а завернуть её в обёртку с overflow-x: auto и показать, что она прокручивается: тень или градиент у правого края. Иначе человек не догадается потянуть.
Первую колонку при этом фиксируют через position: sticky; left: 0 с непрозрачным фоном. Без этого прокрутка бесполезна: уезжает название параметра, и остаются цифры без подписей.
Для таблицы из двух колонок есть вариант лучше: перестроить её в список «параметр — значение», сняв табличное отображение медиавыражением. Читается это на телефоне заметно легче, а разметка остаётся той же — для поиска ничего не меняется.
Фильтр: шторка, а не боковая колонка
На десктопе фильтр — колонка слева. На мобильном её убирают в шторку, и здесь возникают четыре независимые проблемы, каждая из которых встречается сама по себе.
- Высота. <code>100vh</code> в мобильном Safari считается по экрану без учёта свернувшейся адресной строки, поэтому низ шторки с кнопкой «Показать» уезжает под неё. Нужен <code>100dvh</code>, а для старых браузеров — запасной вариант с переменной, которую выставляет JS
- Блокировка прокрутки фона. <code>overflow: hidden</code> на body в iOS не держит прокрутку и теряет позицию: при закрытии шторки человек оказывается в начале списка. Рабочий приём — <code>position: fixed</code> на body с запоминанием и восстановлением scrollY
- Кнопка применения должна быть видна всегда: липкая полоса внизу шторки с учётом <code>env(safe-area-inset-bottom)</code>, иначе на iPhone с жестовой полосой её частично перекрывает
- Счётчик найденного. Человек должен видеть, сколько товаров останется, до нажатия — иначе фильтр превращается в лотерею с перезагрузкой на каждое касание
Отдельно про сам список значений. Чекбокс с областью нажатия 20 пикселей на телефоне не работает: нажимать надо по всей строке, а строка должна быть высотой не меньше 44 пикселей. Это не дизайнерская придирка, а минимальная область касания из рекомендаций и Apple, и Google — ниже неё растёт доля промахов, и человек попадает не в тот фильтр.
Если после применения фильтра страница прокручивается наверх и человек теряет место, где смотрел, — фильтром пользоваться перестанут. На мобильном это дороже любой ошибки вёрстки.
Формы и клавиатура
- <code>font-size</code> у поля ввода не меньше 16 пикселей, иначе iOS Safari зумит страницу при фокусе — и обратно не отзумливает
- <code>type="tel"</code> для телефона, <code>inputmode="numeric"</code> для количества и индекса, <code>type="email"</code> для почты: от этого зависит, какую клавиатуру покажет телефон
- <code>autocomplete</code> с правильными токенами — name, tel, email, postal-code, street-address. Без них автозаполнение не срабатывает, и адрес набирают руками
- Маска ввода не должна мешать вставке из буфера: скопированный номер с +7 и пробелами часто просто не принимается, а перенабирать его на телефоне никто не будет
- Модальное окно с формой: при открытии клавиатуры уменьшается visualViewport, но <code>position: fixed</code> этого не замечает, и поле уезжает под клавиатуру. Лечится подпиской на <code>visualViewport.resize</code>
- Ошибки валидации — над полем или под ним, но в пределах видимой области. Сообщение, которое появляется вверху формы, под клавиатурой не видно вообще
Больше половины проблем мобильной формы — это не вёрстка, а четыре атрибута: type, inputmode, autocomplete и размер шрифта. Проставляются они за полчаса, а проверяются почему-то в последний день перед запуском.
Hover, которого на телефоне нет
Стили наведения без обёртки в @media (hover: hover) на тач-устройствах залипают: человек нажал на карточку, вернулся назад — а карточка осталась подсвеченной, как будто на ней курсор. Выглядит как баг, и это он и есть. Все правила с :hover в каталоге должны лежать внутри этого медиавыражения, а отклик на касание описываться через :active.
Тот же корень у второй проблемы — выпадающее меню, раскрывающееся по наведению. На телефоне первое касание открывает подменю, а переход по самой ссылке требует второго. Часть людей до второго касания не доходит и решает, что ссылка не работает.
Что происходит с картинками
Каталог — это в первую очередь картинки, и на мобильном они решают сразу и скорость, и стабильность раскладки.
- <code>srcset</code> вместе с <code>sizes</code>: без sizes браузер считает, что картинка шириной во весь экран, и скачивает версию крупнее нужной — на плитке из двух колонок это двукратный перерасход
- Размеры или <code>aspect-ratio</code> на каждом изображении, иначе список товаров прыгает по мере загрузки, и это прямой CLS
- <code>loading="lazy"</code> на всём, кроме первой видимой картинки; на неё наоборот <code>fetchpriority="high"</code>
- WebP или AVIF с запасным JPEG через <code>picture</code> — и один и тот же файл не должен весить 400 килобайт ради плитки 160 пикселей шириной
- Зарезервированное место под баннеры и блоки, которые подгружаются скриптом: пустой div нужной высоты дешевле, чем прыжок раскладки
Замеры делаются на карточке товара и на категории с открытым фильтром, а не на главной: на главной половина этого кода не работает. Про порядок работы с метриками и про то, что чинить первым, мы разбирали подробно в статье про Core Web Vitals для каталога.
Мелочи, которые ломают путь к заказу
- Хлебные крошки в одну строку с длинными названиями категорий: либо прокрутка самих крошек, либо схлопывание середины в многоточие
- Цена и кнопка «В корзину» в одном ряду: на 320 кнопка сжимается до нечитаемой. Ставить в столбик
- Сортировка кастомным дропдауном: нативный select на мобильном почти всегда удобнее, потому что открывается системным колесом и не требует прокрутки внутри страницы
- Пагинация с номерами страниц по 24 пикселя: мимо промахиваются все. Либо крупные кнопки, либо подгрузка по нажатию
- Липкая шапка высотой 80 пикселей съедает на невысоком экране седьмую часть области просмотра. На мобильном шапка должна быть ниже десктопной, а не такой же
- Всплывающее окно с подпиской, у которого крестик 16×16 в углу, — это окно, которое не закрывается
Как это проверять
Режим устройства в инструментах разработчика показывает раскладку, но не показывает главного: как ведёт себя клавиатура, как работает инерционная прокрутка, попадает ли палец по кнопке. Поэтому проверка идёт в три уровня.
- Инструменты разработчика на ширине 320 и 360: ищем горизонтальный скролл, перекрытия и обрезанный текст
- Реальный телефон через удалённую отладку — chrome://inspect для Android, Web Inspector для iOS. Только так видно клавиатуру, фиксированные элементы и безопасные зоны
- Полный путь целиком: категория → фильтр → карточка → корзина → оформление. Ошибки почти всегда сидят на стыках, а не внутри шаблонов
И один отчёт в Метрике, про который забывают: конверсия в разрезе устройств и разрешений экрана. Если мобильная конверсия втрое ниже десктопной при сопоставимом трафике, дело не в качестве посетителей. Именно с этого обычно начинается юзабилити-аудит — он показывает, где люди отваливаются, до того как что-то переделывать.
Про индексацию: что нельзя прятать
Google индексирует мобильную версию страницы. То, чего на мобильном нет, для него не существует — поэтому скрывать «лишнее» на узких экранах через display: none опасно. Описание категории, характеристики, текстовый блок под каталогом, перелинковка должны остаться в разметке. Свернуть их в аккордеон можно: содержимое в коде есть, просто не раскрыто. Вырезать — нельзя.
Яндекс к этому относится мягче, но удобство на мобильных учитывает отдельно. Так что практика для обоих одна: контент тот же, подача другая.
Порядок работ
- Найти и убрать горизонтальный скролл — одним проходом по консоли на трёх типах страниц
- Привести области нажатия к 44 пикселям: строки фильтра, пагинация, крестики, ссылки в подвале
- Починить шторку фильтра: dvh, блокировка фона без потери позиции, липкая кнопка, счётчик найденного
- Проставить type, inputmode, autocomplete и 16 пикселей во всех формах
- Убрать hover за медиавыражение, отклик на касание перевести на :active
- Закрыть CLS: размеры у картинок, место под баннеры и подгружаемые блоки
- Пройти путь к заказу на реальном телефоне и починить стыки между шаблонами
Порядок именно такой не случайно: первые два пункта дают самый заметный эффект и не требуют менять дизайн, а последний ловит то, что не видно ни в одном инструменте.
Мы делаем каталоги, где мобильная версия — отдельная проработанная раскладка, а не адаптив по остаточному принципу: смотрите разработку сайтов и дизайн сайта, где мобильные экраны рисуются наравне с десктопными. Если каталог уже работает и переделывать его целиком не хочется, начинать дешевле с юзабилити-аудита, а точечные правки по его итогам укладываются в поддержку сайта.