Современный мир диктует разработчикам и дизайнерам довольно высокие стандарты качества и удобства использования веб-приложений. Как правило, хорошее впечатление о приложении складывается из множества мелочей, которые должны гармонично сочетаться между собой. Одной из таких мелочей может являться инерционное движение “драгабельных” (перетаскиваемых) объектов на странице — об этом мы и поговорим в рамках сегодняшней небольшой статьи. Фича особенно актуальна при взаимодействии пользователя с приложением посредством тач-устройств, так как размер экранов таких устройств ограничен, и передвигать объект из одной точки в другую хочется одним “легким движением руки”, а не многократными касаниями экрана.
Подобным вопросом мы когда-то задались в рамках разработки API карт 2GIS, а сегодня решили поделиться нашим скромным опытом.
Прежде чем рассматривать детали реализации инерции, давайте взглянем на уже готовые примеры:
Хорошо, теперь к делу.
Пространство для экспериментов
В качестве тестового примера добавим инерционное перемещение к одному из картографических API, у которого еще нет такой фичи. Таким образом мы: а) чему-то научимся; б) нанесем кому-то необратимую пользу.
Давайте искать столь “обделенное” API. Bing, Google? Нет, у этих ребят с перетаскиванием карты все в порядке. Посмотрим, как обстоят дела у OpenLayers 3. Да, наш “пациент”, над ним и будем ставить эксперименты.
Несмотря на молодость “подопытного”, установка трудностей не вызвала, все как в документации:
Исходный код всех html, js и css файлов примеров можно найти в папке ol3/examples.
Наброски алгоритма
На самом деле, все довольно просто.
Подготовка к инерционному движению:
- отлавливаем момент начала перетаскивания карты и записываем текущее время;
- отлавливаем момент окончания перетаскивания (пользователь отпустил кнопку мышки) и в этот момент:
- меряем продолжительность перетаскивания (в мс.);
- меряем дистанцию, которая была пройдена по оси Х и по оси Y (в пикс.);
- считаем скорость перетаскивания карты по оси Х и по оси Y (в пикс. за мс.):
- скорость = дистанция / продолжительность.
- считаем начальный импульс по оси Х и по оси Y:
- импульс = масса * скорость, где масса — это просто константа.
- вызываем метод, который отвечает за инерционное движение карты по окончанию перетаскивания, назовем его inertiaMove.
Метод inertiaMove вызывается раз в определенный период (каждых 16 мс., например). В этом методе мы:
- сдвигаем центр карты на значение импульса по оси Х и по оси Y;
- уменьшаем импульс, разделив его на коэффициент затухания (константа);
- проверяем, не “скатился” ли импульс по оси Х и по оси Y до определенного предела (тоже константа), если “скатился” — останавливаем движение.
“Экстренная” остановка перетаскивания. Карта должна принудительно перестать двигаться в двух случаях:
- если пользователь кликнул мышкой по карте;
- если пользователь изменил масштаб карты.
Код
Так как OpenLayers 3 сделан на основе Closure library, будем писать код по его правилам.
Взглянув на файловую структуру, мы увидим, что в каталоге interactions находятся все объекты, которые отвечают за взаимодействие с картой. Среди них есть объект ol.interaction.DragPan, отвечающий за перетаскивание, создадим и отнаследуем от него наш ol.interaction.DragPanInertia и реализуем в нем инерционное движение карты.
Также для удобства была добавлена реализация polyfill-а для метода requestAnimationFrame от Erik Möller.
Что касается принудительной остановки движения карты, делается это весьма простой подпиской на соответствующие события:
var eventType = ol.BrowserFeature.HAS_TOUCH ? goog.events.EventType.TOUCHEND : goog.events.EventType.MOUSEDOWN;
// mouse down
goog.events.listen(this._map.getViewport(), eventType, this.stopInertiaMove, false, this);
// zoom changed
goog.events.listen(this._map, ol.Object.getChangedEventType(ol.MapProperty.RESOLUTION), this.stopInertiaMove, false, this);
Для того, чтобы карта начала использовать реализованные нами объекты, их необходимо зарегистрировать в методе createInteractions объекта ol.Map.
Сначала подключаем нашу реализацию, заменив
goog.require('ol.interaction.DragPan');
на
goog.require('ol.interaction.DragPanInertia');
А потом строку
interactions.push(new ol.interaction.DragPan(ol.interaction.condition.noModifierKeys));
заменяем на
interactions.push(new ol.interaction.DragPanInertia(ol.interaction.condition.noModifierKeys));
Как вы уже наверное заметили, реализованный нами код лежит на github-е, а, благодаря github pages, вы можете посмотреть в действии пример карты с инерцией при передвижении. Пример лучше всего смотреть в Google Chrome, так как под различные браузеры и устройства он не оптимизировался, это не является основной целью статьи. Что же касается самого OpenLayers 3, то библиотека находится в стадии активной разработки и использовать ее в “боевых” приложениях пока не стоит (имеются проблемы при работе с тач-устройствами, а также со стабильностью самого API). Тем не менее, пожелаем разработчикам выдержки, вдохновения и хороших pull request-ов в столь полезный для пользователей проект.
Автор: AndreyGeonya