8 lines
19 KiB
JSON
8 lines
19 KiB
JSON
{
|
||
"index": 191,
|
||
"slug": "editorial-2022-09-mechanism-mobile-ux",
|
||
"title": "Мобильный интерфейс без скрытого действия: как разделить среду и маршрут",
|
||
"excerpt": "На узком экране действие часто исчезает не из-за одного breakpoint. Разбираем границу между возможностями ввода, состоянием компонента и обязательным маршрутом, а затем проверяем отрицательный путь.",
|
||
"contentHtml": "<p>На десктопе карточка показывает «Открыть итог» после наведения. На телефоне указателя нет, а текстовой кнопки нет тоже. Человек видит данные, но не видит следующего шага. Похожий сбой возникает у таблицы: действие остаётся в последней колонке за пределами экрана. Ещё один вариант — подтверждение появляется только после hover, хотя экранная клавиатура уже закрыла часть интерфейса. Цена ошибки — незавершённая операция, повторное обращение в поддержку или неверное подтверждение без нужного контекста.</p>\n<p>Один media query не исправляет этот класс проблем. Он меняет раскладку, но не отвечает, какое действие должно остаться доступным. Рабочий тезис проще: возможности среды подсказывают адаптацию, а маршрут компонента должен явно описывать обязательные шаги. Нельзя строить единственный путь на hover, широкой таблице или предположении о двух руках.</p>\n<h2>Механизм: четыре границы одного экрана</h2>\n<p>Сначала есть среда. Media Queries описывают характеристики user agent и устройства: ширину, pointer и hover capability. Это свойства среды, а не заключение о том, что конкретному человеку удобно. На одном устройстве могут быть доступны разные способы ввода. Поэтому <code>hover: none</code> не означает «в системе никогда не будет hover», а <code>pointer: coarse</code> не измеряет размер пальца и точность действия.</p>\n<p>Дальше работает компонент. Он строит summary, details, confirm и состояния loading, error, disabled. Его задача — сохранить порядок и смысл действия в конкретной разметке. Затем продукт решает, какой переход является главным. Если подтверждение обязательно, его нельзя оставлять единственным эффектом наведения. Наконец, отдельная проверка реализации показывает, как этот контракт ведёт себя в браузере и с реальным содержимым.</p>\n<table><caption>Граница ответственности в мобильном маршруте</caption><thead><tr><th scope='col'>Слой</th><th scope='col'>Что известно</th><th scope='col'>Что можно решить</th><th scope='col'>Чего нельзя заключать</th></tr></thead><tbody><tr><td>Среда</td><td>Доступны значения media features или события pointer</td><td>Подобрать раскладку и дополнительное улучшение</td><td>Что конкретному человеку удобно действовать</td></tr><tr><td>Компонент</td><td>Определены шаги и состояния маршрута</td><td>Показать явный control и сохранить контекст</td><td>Что разметка уже доступна во всех браузерах</td></tr><tr><td>Продукт</td><td>Названо обязательное действие</td><td>Запретить hover-only как единственный переход</td><td>Что изменится конверсия или скорость</td></tr><tr><td>Проверка</td><td>Есть условия, сценарий и ожидаемый результат</td><td>Провести browser, keyboard или accessibility check</td><td>Что локальная функция заменила исследование</td></tr></tbody></table>\n<p>Такое разделение локализует ошибку. Если кнопка исчезла, можно проверить CSS visibility, состояние компонента, overflow и сам продуктовый маршрут. Без границы всё называют «мобильным UX», а исправление сводится к случайному breakpoint. После него экран может выглядеть аккуратно и всё равно оставаться незавершённым.</p>\n<figure><img src='/assets/editorial/2022/mobile-ux-2022-contract.svg' alt='Четыре слоя мобильного маршрута: среда ввода, маршрут компонента, продуктовое решение и отдельная проверка' loading='lazy' /><figcaption>Схема отделяет capability среды от маршрута и проверки. Профиль narrow/coarse/no-hover в примере задаётся явно и не считывается из браузера.</figcaption></figure>\n<h2>Пример: контракт не принимает скрытый переход</h2>\n<p>Ниже приведена учебная функция. Она не создаёт DOM, не запускает браузер, не читает <code>matchMedia</code>, не получает pointer events и не измеряет удобство. Профиль — входные данные примера. Функция проверяет только одно правило: на узком маршруте главное действие должно иметь явный control. Этот код не является production-адаптером и не заменяет browser test.</p>\n<pre><code>function planMobilePath(profile, action) {\n const empty = {\n accepted: false,\n platform: 'not-observed',\n usability: 'not-measured',\n };\n\n const validProfile = profile\n && ['narrow', 'wide'].includes(profile.viewport)\n && ['coarse', 'fine'].includes(profile.primaryPointer)\n && ['available', 'unavailable'].includes(profile.hover);\n\n if (!validProfile || !action?.id || !action?.label) {\n return { ...empty, reason: 'invalid-contract' };\n }\n\n const requiresExplicitControl =\n profile.viewport === 'narrow'\n || profile.primaryPointer === 'coarse'\n || profile.hover === 'unavailable';\n\n const steps = ['summary', 'details', 'confirm'];\n\n if (action.activation === 'hover-only' && requiresExplicitControl) {\n return {\n ...empty,\n reason: 'hover-only-blocked',\n steps,\n rollback: 'keep-current-path',\n };\n }\n\n return {\n ...empty,\n accepted: true,\n reason: 'explicit-route-accepted',\n steps,\n };\n}\n\nconst profile = {\n viewport: 'narrow',\n primaryPointer: 'coarse',\n hover: 'unavailable',\n};\n\nplanMobilePath(profile, {\n id: 'confirm-order',\n label: 'Подтвердить заказ',\n activation: 'explicit-control',\n});\n// accepted: true, steps: summary -> details -> confirm\n\nplanMobilePath(profile, {\n id: 'open-summary',\n label: 'Открыть итог',\n activation: 'hover-only',\n});\n// accepted: false, reason: hover-only-blocked</code></pre>\n<p>В первой ветке функция принимает маршрут, потому что действие имеет метку и явный способ активации. Во второй она возвращает отказ. Это полезная локальная проверка: код не может случайно объявить hover-only допустимым для выбранного профиля. Но результат ничего не говорит о настоящем viewport, фокусе, размерах touch target, screen reader, локали или сетевой задержке.</p>\n<p>Отрицательный путь здесь важнее зелёной ветки. Неполный профиль возвращает <code>invalid-contract</code>. Пустая метка также останавливает переход. Для hover-only сохраняется <code>keep-current-path</code>. Это не автоматический rollback приложения. Это запрет считать новый маршрут принятым. Реальный откат выбирают отдельно: feature flag, возврат коммита, старый route или ограниченный rollout.</p>\n<h2>Симптом → причина → проверка → действие</h2>\n<table><caption>Диагностическая карта скрытого действия</caption><thead><tr><th scope='col'>Симптом</th><th scope='col'>Причина</th><th scope='col'>Проверка</th><th scope='col'>Действие</th></tr></thead><tbody><tr><td>Меню видно только при наведении</td><td>Главный переход привязали к hover</td><td>Есть ли видимая кнопка или ссылка в маршруте</td><td>Добавить explicit control и оставить контекст</td></tr><tr><td>Кнопка находится справа за экраном</td><td>Действие закрепили за широкой таблицей</td><td>Проверить overflow и порядок summary/details/confirm</td><td>Вынести confirm в доступный поток</td></tr><tr><td>На hybrid input состояния расходятся</td><td>Primary capability приняли за все доступные способы ввода</td><td>Сравнить primary и any capability, не делая вывод о человеке</td><td>Оставить hover как улучшение, но не как единственный путь</td></tr><tr><td>После адаптации пропал контекст</td><td>Сразу сжали layout и спрятали обязательные данные</td><td>Проверить, что summary виден до действия</td><td>Вернуть краткий итог и вынести детали в отдельный шаг</td></tr><tr><td>Изменение нельзя безопасно отменить</td><td>Удалили старую ветку до проверки нового маршрута</td><td>Есть ли сохранённый current path и владелец отката</td><td>Сначала сохранить обратимый переход</td></tr><tr><td>Локальный тест зелёный, экран сломан</td><td>Контракт приняли за проверку реализации</td><td>Повторить сценарий в браузере с фактическими данными</td><td>Добавить browser, keyboard или accessibility check</td></tr></tbody></table>\n<h2>Порядок действий</h2>\n<ol><li><strong>Опишите симптом.</strong> Назовите конкретное состояние: что не видно, какой переход требует hover, где появляется горизонтальная прокрутка и какое действие нельзя завершить.</li><li><strong>Зафиксируйте условия.</strong> Укажите экран, данные, локаль, viewport, масштаб, способ ввода и состояние компонента. Не подставляйте значения, которых никто не наблюдал.</li><li><strong>Разделите причину.</strong> Проверьте media rule, DOM, visibility, overflow, component state и продуктовое решение. Не объявляйте любую проблему проблемой ширины.</li><li><strong>Опишите маршрут.</strong> Запишите summary, details, confirm, отмену, loading и error. Для каждого шага назовите видимый control и ожидаемый результат.</li><li><strong>Проверьте отрицательную ветку.</strong> Передайте неполный профиль, пустую метку и hover-only. Ожидайте отказа, а не молчаливого построения нового пути.</li><li><strong>Проверьте среду отдельно.</strong> Если реализация использует media features или pointer events, получите фактические значения в браузере и сохраните условия запуска.</li><li><strong>Проверьте реализацию.</strong> Пройдите маршрут клавиатурой, на узком и широком контейнере, с длинным текстом и ошибкой. Убедитесь, что focus и контекст не исчезают.</li><li><strong>Согласуйте откат.</strong> Определите, как вернуть current path, кто принимает решение и какие данные нельзя потерять. Не называйте поле <code>rollback</code> готовым откатом сервера.</li></ol>\n<h2>Ограничения</h2>\n<p>Профиль <code>narrow</code> не равен конкретным 320, 375 или 412 CSS-pixels. Реальный breakpoint зависит от содержимого, шрифта, локали, масштаба и layout. Его нельзя вывести из этой функции. Значения <code>coarse</code> и <code>unavailable</code> также не описывают навык человека и не заменяют проверку assistive technology.</p>\n<p>Media Queries помогают выбрать presentation, но не определяют бизнес-логику завершения операции. Pointer Events описывают модель событий, но не обещают, что control заметен или физически достижим. WCAG задаёт проверяемые критерии для конкретного контента и реализации, а соответствие нельзя вывести из одной unit-проверки. В учебном примере нет production-трафика, пользовательских наблюдений, конверсии и доказательства доступности.</p>\n<p>Нельзя считать любой explicit control достаточным. Он может быть слишком мал, терять фокус, иметь неясную метку, открывать устаревшие details или отправлять действие повторно. Нельзя исправлять скрытый маршрут только увеличением кнопки. Сначала восстановите смысл и порядок, затем проверяйте визуальные и технические свойства.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Маршрут готов к следующей проверке, если другой инженер без устного объяснения может показать summary, открыть details, выполнить confirm, увидеть loading и error, отменить действие и назвать путь отката. Главный переход доступен без hover. Условия браузерной проверки записаны. Длинный текст и узкий контейнер не скрывают control. Локальная модель отклоняет неполный профиль и hover-only.</p>\n<p>Это ещё не заявление о доступности всего продукта и не доказательство роста метрики. Для такого вывода нужны отдельные browser, accessibility и usability-проверки с их методами и ограничениями. Проверяемый результат этой статьи уже: обязательное действие не зависит от одной capability, а граница между моделью и реальной средой остаётся явной.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href='https://www.w3.org/TR/mediaqueries-4/' target='_blank' rel='noopener noreferrer'>W3C: Media Queries Level 4</a> — официальное описание media features, включая pointer, hover, any-pointer и any-hover. Спецификация описывает среду, а не удобство конкретного человека.</li><li><a href='https://www.w3.org/TR/pointerevents3/' target='_blank' rel='noopener noreferrer'>W3C: Pointer Events Level 3</a> — официальная модель pointer events и их свойств. Она не доказывает достижимость control и не заменяет проверку маршрута.</li><li><a href='https://www.w3.org/TR/WCAG22/' target='_blank' rel='noopener noreferrer'>W3C: Web Content Accessibility Guidelines 2.2</a> — актуальная Recommendation с критериями, которые проверяют на конкретной странице и в конкретной реализации. Учебный код выше не является заявлением о соответствии.</li></ul>"
|
||
}
|