DarkRiDDeR11 мин

Мобильный сценарий: один путь без зависимости от hover

UXFrontend

Десктопный поток часто ломается на телефоне не из-за одного маленького шрифта. В нём может быть меню, которое открывается только при наведении, таблица с действием в последней колонке или подтверждение, доступное лишь рядом с широким экраном. Человек не видит следующего шага и не может закончить действие. Цена ошибки конкретна: незавершённая форма, лишнее обращение в поддержку или изменение, которое никто не может повторить по описанию.

Начинать с «сделаем mobile version» бесполезно. Сначала выбираем один путь: например, проверить данные и подтвердить операцию. Для него записываем, что должно быть видно до действия, что открывается после него и чем можно активировать переход. Ниже — учебный контракт. Он не запускает браузер и не доказывает, что кому-то удобно. Он только не даёт коду принять маршрут, в котором на узком экране единственный переход спрятан за hover.

Один путь вместо переделки всей страницы

У пути есть три состояния: краткий итог, доступные детали и явное подтверждение. Итог не обязан повторять все поля; он даёт контекст, чтобы не нажать кнопку вслепую. Детали раскрываются отдельным контролом, а действие имеет текстовую метку. В этой схеме «явный» значит, что контракт описывает контрол как видимый шаг. Это не означает, что элемент уже имеет правильный размер, фокус, семантику или раскладку: такие свойства проверяются в реализации отдельно.

Контракт одного маршрута
Часть путиЧто обязан увидеть человекЧего контракт не обещаетПроверка в fixture
summaryназвание действия и краткий контекстчто текст поместится в любом языкешаг существует первым
detailsявный переход к данным перед подтверждениемчто accordion удобен конкретному человекушаг существует вторым
confirmконтрол «Продолжить» с видимой меткойразмер hit-area и реакцию браузерана narrow/coarse/no-hover требует explicit control
rollbackстарый путь остаётся, пока новый контракт не принятоткат сервера или экспериментадля blocked route возвращается keep-current-path

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

Пример использует строки narrow, coarse и unavailable как входные данные. Они не считываются через matchMedia, не соответствуют модели устройства и не являются результатом real trace. Поэтому fixture можно запустить в Node и получить один и тот же результат. Она годится для ревью договора о маршруте, а не для ответа на вопрос «работает ли сайт на конкретном телефоне».

const profile = {
  viewport: 'narrow',
  primaryPointer: 'coarse',
  hover: 'unavailable',
};

const route = planTeachingMobilePath(profile, {
  id: 'confirm-order',
  label: 'Подтвердить заказ',
  activation: 'explicit-control',
});

console.log(route.accepted); // true
console.log(route.steps.map(({ id }) => id));
// ['summary', 'details', 'confirm']

const rejected = planTeachingMobilePath(profile, {
  id: 'open-summary', label: 'Открыть итог', activation: 'hover-only',
});
console.log(rejected.reason); // hover-only-blocked-by-contract

Важная отрицательная ветка находится рядом: hover-only на этом профиле отклоняется. Это не правило CSS и не интерпретация W3C как готового дизайна. Это выбранная продуктовая оговорка одного учебного пути: если мы заранее заявили отсутствие удобного hover, основное действие нельзя сделать достижимым только так. На широком profile модель допускает такой способ, но всё равно честно пишет usability: not-measured.

Схема одного учебного мобильного маршрута: summary ведёт к details, затем к видимому confirm; ветка hover-only на narrow, coarse и unavailable останавливается и сохраняет текущий путь.
Рисунок показывает проектный контракт переходов. Он не является картой экрана, браузерным снимком или результатом usability-теста.

Почему hover нельзя считать маршрутом

Media Queries Level 4 различает способность primary pointing device к hover и возможности всех pointing devices. Из этого не следует, что сайт может узнать намерение человека или что на устройстве никогда не появится мышь. Документ прямо советует не делать layout полностью зависимым от hovering. Для редактора это полезная граница: conditional styling допустим, но главный путь должен иметь наблюдаемую альтернативу, описанную без надежды на случайный long press.

Pointer Events Level 2 даёт общий словарь pointer input, но не гарантирует, что пользователь выполнит жест или увидит подсказку. WCAG 2.1 также не превращает один code sample в доказанное соответствие. Поэтому в этой статье нет выдуманной диагонали экрана, среднего пальца, конверсии и «проверили на iPhone». Такие данные появились бы только после отдельного исследования с условиями, участниками и артефактами.

Маршрут: симптом → причина → проверка → действие

  1. Симптом. Запишите одно недоступное действие: например, итог открывается только при hover или кнопка стоит за таблицей, которую нельзя читать без горизонтального поиска.
  2. Причина. Найдите, какой элемент владеет переходом и есть ли у него видимая текстовая альтернатива. Не называйте это «проблемой мобильности» до этого шага.
  3. Проверка договора. Выполните node web/scripts/upgrade-2022-09.mjs --verify-fixture. PASS означает лишь то, что local model отклоняет hover-only на выбранном profile и сохраняет порядок summary → details → confirm.
  4. Действие. В реализации добавьте явный control, оставьте context перед подтверждением и договоритесь, что именно является необратимым действием.
  5. Проверка реализации. Отдельно зафиксируйте browser, viewport, способ ввода, язык, масштаб, данные формы и ожидаемые видимые состояния. Только так начинается реальная проверка UI.
  6. Откат. Если новый путь скрывает важные данные или меняет предметное действие, верните прежний route contract; не удаляйте старую ветку до проверки продукта и владельца операции.

Что проверить в коде до выпуска

Я бы начал с DOM-структуры: label действия, порядок controls, видимое раскрытие details и состояние после активации. Затем проверил бы keyboard path, потому что отсутствие hover не отменяет другие способы ввода. После этого имеет смысл смотреть CSS: нет ли правила, которое делает единственную кнопку прозрачной, выносит её за scroll-area или показывает только в :hover. Для этого нужен отдельный browser test; модель ниже таких данных не содержит.

Если команда использует media queries, полезно хранить их как адаптацию интерфейса, а не как определитель «телефона». Датированный CRD предлагает query к свойству среды, а не к личности пользователя. Поэтому условие @media (hover: none) может включить дополнительную подсказку или всегда видимую панель, но не должно отменять основное действие для тех, кто подключил иной указатель или пользуется клавиатурой.

Граница и следующий шаг

Контракт не измеряет время, не вызывает CSS, не проверяет target size и не даёт продуктовой метрики. Его action labels, три шага и branch rollback — проектные значения этого материала. Если в реальной операции есть авторизация, цена повторного подтверждения или серверный статус, их надо добавить в отдельный контракт. Нельзя назвать модель полным usability-test только потому, что в ней есть слово mobile.

Следующий шаг — взять один настоящий экран и провести совместный review с дизайнером, разработчиком и владельцем операции. На бумаге или в прототипе перечислите summary, details, confirm, отмену и ошибки. Затем напишите browser-check с фиксированными условиями. Если проверка расходится с учебным route, меняйте контракт осознанно и добавляйте новую assertion; не подгоняйте результат под красивую схему.

Историческая граница сентября 2022

В пакете используются только доступные к сентябрю 2022 датированные документы W3C: Media Queries Level 4 CRD от 25 декабря 2021 года, Pointer Events Level 2 Recommendation от 4 апреля 2019 года и WCAG 2.1 Recommendation от 5 июня 2018 года. Они задают терминологию и ограничения, а не данные о реальном устройстве, браузере или пользователях.

Проверяемые источники