Поддержка Touch в JavaScript
- четверг, 16 апреля 2020 г. в 00:27:42
Какие проблемы могут быть у frontend-программиста, если тестировщик запустит его приложение на iPad с новой трекпад-клавиатурой, Windows-планшете, с неопределенным состоянием “режима планшета” или ноутбуке с подключенным к нему телевизором c поддержкой Multi-touch?
Это далеко не полный список допустимых конфигураций оборудования, которые мы поддерживаем при разработке системы СБИС. Сегодня СБИС — это не только знакомое многим решение для сдачи отчетности, ведения электронного документооборота и бухгалтерии, но и набор инструментов для автоматизации розницы, общепита, доставки и логистики. В этих сферах нужно уметь хорошо работать на самых разных планшетах и гаджетах с различными экранами и типами устройств ввода. И далеко не всегда проблемы могут быть связаны с экзотическим сочетанием настроек операционных систем и драйверов: если взять обычный iPad с браузером Safari, Android планшет или ноутбук-трансформер на Windows10 с последней версией Google Chrome — везде будет свой набор ошибок и особенностей обработки пользовательского ввода.
Эта статья о том, как, а главное, зачем вводить в обычных Web приложениях режим поддержки Touch.
Основными особенностями Touch режима при организации пользовательского взаимодействия является отсутствие указателя мыши и появление виртуальной клавиатуры.
Соответственно на уровне приложения необходимо предусмотреть:
Мы разрабатываем собственную библиотеку визуальных компонентов, поэтому стараемся решать все проблемы по максимуму на уровне фреймворка.
Важно отметить, что при взаимодействии с одним и тем же устройством пользователь может переходить из одного режима взаимодействия в другой. Например открепить экран от ноутбука-трансформера, поработать с ним как с планшетом, а затем прикрепить обратно и продолжить работать с помощью мыши.
Это означает, что заведомо мы никогда не знаем в каком режиме используется устройство.
Еще пример: в наших переговорках к ноутбукам подключены телевизоры с поддержкой Multi-touch. Формально браузер на ноутбуке считает, что его устройство поддерживает Touch-события. Именно на этой конфигурации оборудования мы обнаружили проблему в нашем первом алгоритме определения Touch-режима по наличию поддержки специализированных событий. Несмотря на то, что телевизор закреплен на стене, а на ноутбуке мы работаем в обычном режиме с подключенной мышью, приложение переключилось в режим, адаптированный для работы на планшете.
Сегодня мы определяем Touch-режим путем анализа реакции на события touchstart и mousemove: если вызову mousemove не предшествовал touchstart, значит пользователь ведет мышкой, иначе нажал пальцем.
Исходный код примерно такой
touchstartHandler() {
this._isTouch = null;
}
mousemoveHandler() {
if (this._isTouch === null) {
this.updateState(true);
} else if (this._isTouch) {
this.updateState(false);
}
}
updateState(state) {
this._isTouch = state;
this._triggerEvent();
}
В результате генерируется событие и мы сохраняем значение режима в объекте с информацией о текущем окружении, а также на body выставляем классы is-touch или is-hover.
В нашем приложении мы полностью отказываемся от поддержки :hover при работе в Touch режиме. Все стили с поддержкой :hover выглядят примерно так:
body.is-hover .my-button:hover {
}
Этот способ работает надежнее стандартного @media (hover: hover), который не отключает поддержку hover свойства в Chrome и Firefox при управлении пальцами.
При такой организации стилей мы никогда не получим случайно подсвеченный через :hover элемент под виртуальным курсором мыши, оставшемся в точке последнего взаимодействия.
Следует обратить особое внимание на использование этого псевдокласса для управления видимостью блока.
Наши компоненты списков, таблиц и деревьев используют :hover-селекторы для показа операций над строкой при наведении (например редактировать, удалить и т.п.).
Подобное применение :hover на iOS приведет к тому, что первый клик по строке только изменит видимость элементов управления, поэтому пользователю придется делать второй клик. А вот на Android и Windows операции над строкой никто никогда не увидит.
При переходе в Touch-режим в списочных компонентах отключается поддержка :hover и включается поддержка управления жестами, например swipe или долгое нажатие.
Swipe-жесты мы обрабатываем самостоятельно по событию mousemove в touch-режиме при следующих условиях:
К сожалению, даже в последних версиях Google Chrome периодически возникают проблемы с виртуальной клавиатурой.
Иногда мы сами регистрируем ошибки:
Ссылка 1
Ссылка 2
Иногда находим на уже зарегистрированные:
Ссылка 1
Ссылка 2
В демо-примерах проблема может выглядеть безобидной, но в реально работающем приложении все скачет в разные стороны и по несколько раз. Поэтому, при наличии возможности, мы стараемся размещать поля ввода выше предполагаемой границы виртуальной клавиатуры. Это позволяет сгладить эффект от ряда существующих ошибок и минимизировать риски при появлении новых при обновлении браузера. В идеале уложиться в 300px по высоте, чтобы обеспечить комфортную работу даже на маленьких POS-терминалах.
Самый простой пример: как не нужно размещать поля логин и пароль на форме авторизации
Если устройство имеет экран побольше, то становится актуальным вопрос удобного отображения подсказок у поля ввода при вводе текста. Подсказки и обычные диалоги нужно по возможности позиционировать в видимой области экрана.
На первый взгляд задача выглядит не сложной, но до недавнего времени она не имела 100% работающего решения:
Во-первых, API браузера не предоставлял информации о том, показана виртуальная клавиатура или нет. Нам приходилось отслеживать приход и уход фокуса для каждого поля ввода. При этом клавиатуру можно просто скрыть и на состояние фокусировки это никак не повлияет.
Во-вторых, никто не знал реальный размер клавиатуры. Мы подобрали коэффициенты в зависимости от ориентации экрана: 0.3 для портретной и 0.55 для ландшафтной.
Но стоило к iPad подключить Smart Keyboard, как все вычисленные коэффициенты оказались бесполезными и даже вредными, т.к. высота виртуальной клавиатуры схлопнулась до одной строки.
К счастью, сегодня все современные браузеры получили поддержку VisualViewport. Прошло целых 12 лет с момента релиза iOS, прежде чем разработчики смогли получить информацию об изменении размера viewport при показе виртуальной клавиатуры.
Новое API позволяет решить сразу 2 проблемы:
День отказа от поддержки iOS12 станет нашим большим праздником, т.к. мы сможем окончательной перейти на поддержку VisualViewport, убрав из кода всю магию.
Старайтесь избегать ручного управления фокусом в коде с асинхронным стеком, например, связанного с загрузкой ресурсов или ожиданием ответа от сервиса. Система iOS может показать виртуальную клавиатуру только в рамках синхронной обработки события действия пользователя. В противном случае клавиатура будет появляться или убираться тогда, когда этого уже никто не ждет.
Подобные ошибки периодически встречаются во всех крупных веб-сервисах. Я лично видел проблемы на iPad в разное время и в Google при поиске картинок, когда клавиатура для поля поиска скрывалась сразу после показа и сама же открывалась обратно, и в Яндекс Маркете, когда клавиатура исчезала во время ввода текста в поле поиска.
Если исходный код вашего приложения полностью загружается при старте приложения, то часть проблем, связанная с асинхронной загрузкой ресурсов вас не касается.
Но если часть ресурсов грузиться по требованию, или после действия пользователя необходимо совершить асинхронный запрос за данными, то проблема становится актуальной. В таком случае, например, после перехода по навигации в SPA-приложении или после открытии диалога, нельзя устанавливать фокус в поле ввода на iOS.
Так у каждого нашего компонента есть метод activate для установки фокуса. По умолчанию активация не устанавливает фокус в поля ввода на iOS-устройствах. При этом можно передать параметр enableScreenKeyboard: true для показа клавиатуры. В этом случае программист должен обеспечить синхронный вызов метода в обработчике пользовательского ввода.
Таким образом, в современных браузерах с помощью нехитрого набора приемов достаточно легко можно организовать поддержку Touch-режима. Это те самые 20% усилий, которые помогут добиться 80% результата: у большинства пользователей приложение будет предсказуемо работать на их конфигурациях оборудования.
Конечно, в нашем коде есть еще много закладок на особенности работы режима Touch в разных браузерах, но все они имеют более глубокую специфику.
Например, как мы решаем проблему не всегда работающего клика при таче, почему поле ввода может улететь под виртуальную клавиатуру и что с этим делать, какая дополнительная поддержка должна быть при работе с резистивным экраном и как его определить, а также много других интересных задач.
Если эта тема будет интересна, я готов продолжить описывать их в следующей статье.