Карта города и справочник предприятий
87,01
рейтинг
17 января 2013 в 10:26

Разработка → Инерция в JavaScript на примере OL3

Современный мир диктует разработчикам и дизайнерам довольно высокие стандарты качества и удобства использования веб-приложений. Как правило, хорошее впечатление о приложении складывается из множества мелочей, которые должны гармонично сочетаться между собой. Одной из таких мелочей может являться инерционное движение “драгабельных” (перетаскиваемых) объектов на странице — об этом мы и поговорим в рамках сегодняшней небольшой статьи. Фича особенно актуальна при взаимодействии пользователя с приложением посредством тач-устройств, так как размер экранов таких устройств ограничен, и передвигать объект из одной точки в другую хочется одним “легким движением руки”, а не многократными касаниями экрана.

Подобным вопросом мы когда-то задались в рамках разработки 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
2ГИС
рейтинг 87,01
Карта города и справочник предприятий

Комментарии (18)

  • +4
    Невиданная гладкость для OL :)
  • 0
    Хм. Я помню, ещё с год назад включал в OL инерциальное тягание. Или его выпилили из новой версии?
    • 0
      • +2
        Это пример из OL2.

        На момент написания статьи механизмов для работы с анимацией, в том числе инерции, в OL3 не было, но планировалось:
        We're doing some major refactoring to the codebase at the moment, which will include a mechanisms for animation
  • +13
    Вы неправильно считаете инерцию, не учитывая, что drag может долго не заканчиваться.
    Use case:
    — пользователь начинает drag, водит нажатой мышой по карте.
    — насмотревшись окрестностей/найдя нужный ракурс, пользователь отпускает кнопку мыши

    Ожидаемый результат:
    — карта никуда не едет, т.к. «импульса» не было (скорость мыши в момент отпускания мала)

    Наблюдаемый результат:
    — карта «улетает» под большим импульсом, пропорционально пути мыши от начала drag-a.

    Предлагается:
    — считать импульс за интервал (например за последние 50-100мс).
    • +1
      Спасибо за дельное замечание.

      Действительно, этот кейс в статье не учтен. В API карт 2GIS-а мы считаем дистанцию (и, как следствие, импульс) не всего пути drag-а, а лишь последнего отрезка пути. Последний отрезок пути считаем исходя из разницы координат последнего события DgDrag и события DgDragStop.

      Более того, если импульс очень маленький, тогда вообще нет смысла инерционно передвигать карту, мы запускаем инерцию только в том случае, если импульс больше определенной константы.
  • 0
    прикольно, так надо и к 2гис-у прикрутить интерцию
    • +2
      Так ведь уже. ;)
      • 0
        да, точно, ступил.

        тогда было бы круто, если бы был плавный зум)
        • 0
          Учтём :)
  • +3
    Ткнул на «посмотреть в действии», увеличил Кремль московский (сильно), решил чуть сдвинуть карту, по инерции оказался в Твери :)
    Нужен допилинг, имхо

    Ubuntu 11.04
    Chrome 18
    • +1
      Может не в тему конечно, но чего Хром такой старенький то?
    • 0
      «Допилинг» нужен, однозначно. Пример реализации не стоит рассматривать как окончательно готовое и единственно правильное решение, пример лучше рассматривать в обучающих целях, для реализации подобных задач в своих проектах. Для полноценного решения в OL3 было бы неплохо дождаться «устаканивания» основных «классов» библиотеки.
  • 0
    Я правильно понял, что точный адрес этот: github.com/AndreyGeonya/ol3/blob/master/src/ol/interaction/dragpaninertiainteraction.js?
    • 0
      Это адрес fork-а от основного репозитория. Сам основной репозиторий OpenLayers 3 находится здесь: github.com/openlayers/ol3/
      • 0
        Я, собственно, спрашивал про исходник вашего компонента
  • 0
    Еще глюк:
    1. Начинаем драг
    2. Не двигая мышу, зумим
    3. Двигаем мышу, не заканчивая драг
    Результат — резкий скачок

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка