/marker-animate-unobtrusive

Google Maps markers become animated, unobtrusively

Primary LanguageJavaScriptMIT LicenseMIT

marker-animate-unobtrusive aka SlidingMarker

Build Status

Enables a Google Maps marker to move from its original position to its destination in a natural, animated way (unlike the current method of suddenly appearing on the destination spot). Please click on the map below in order to see how it works. Select destination on the map to move marker to.

alt tag

It can work in two modes.

  • In one mode, you create SlidingMarker instead of google.maps.Marker. SlidingMarker inherits google's marker, so working with it is pretty straight-forward.

  • In other mode, SlidingMarker replaces google.maps.Marker with itself, so you just can just forget about it and enjoy nice and smooth movement of all of your markers.

In both cases, animated and non-animated marker will behave exactly the same.

marker.getPosition() will always return final destination of marker, and position_changed event will only trigger when movement starts. That's why it called unobtrusive - your current code will not even notice animation, it's completely transparent.

SlidingMarker is a complete rewrite of markerAnimateUnobtrusive library, with much-much more features and greatly improved architecture.

Table of contents

This project is based on excellent marker-animate project by Robert Gerlach.

This project is one big step towards animated OverlappingMarkerSpiderfier.

Install

with npm

npm install marker-animate-unobtrusive

with bower

bower install marker-animate-unobtrusive

TypeScript support

npm install @types/googlemaps --save
npm install @types/marker-animate-unobtrusive --save

Or with typings:

typings install dt~google.maps --global --save
typings install dt~marker-animate-unobtrusive --global --save

No need to import anything. See demo TypeScript project for reference.

include on page

Download SlidingMarker.js minified or debug from Github, and include it in your page.

SlidingMarker depends on Google Maps and jquery libraries, so include them as well.

jquery.easing may be downloaded from here.

Download marker-animate from here.

So dependencies look like follows:

<!-- Dependencies -->

<!-- jquery library and jquery.easing plugin are needed -->
<script src="jquery.min.js"></script>
<script src="jquery.easing.1.3.js"></script>

<!-- we provide marker for google maps, so here it comes  -->
<script src="https://maps.googleapis.com/maps/api/js?sensor=false"></script>

<!-- we use markerAnimate to actually animate marker -->
<script src="markerAnimate.js"></script>

<!-- SlidingMarker hides details from you - your markers are just animated automagically -->
<script src="SlidingMarker.min.js"></script>

Alternatively, the library can be downloaded from CDN:

<script src="https://cdnjs.cloudflare.com/ajax/libs/marker-animate-unobtrusive/0.2.8/vendor/markerAnimate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marker-animate-unobtrusive/0.2.8/SlidingMarker.min.js"></script>

Note: jQuery is soft dependency, you can prefer not to include it. But if you decide not to, you should provide alternative animation routine, and should not include jquery.easing and markerAnimate libraries.

All needed dependencies are in vendor folder.

NW.js

The library is compatible with NW.js. Use it following way:

<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true"></script>
<script type="text/javascript">
  global.google = google;
</script>
<script type="text/javascript" src="app.js"></script>
var SlidingMarker = require('marker-animate-unobtrusive');
var marker = new SlidingMarker();

Use

locally

When you create marker, instantiate SlidingMarker instance with same parameter you used to instantiate google.maps.Marker:

//old code
//var marker = new google.maps.Marker();

//new code
var marker = new SlidingMarker();

globally

If you want to make all markers on map animate, first of all, call SlidingMarker.initializeGlobally() method. Do it as soon as SlidingMarker script loads, before any other script:

<script src="SlidingMarker.min.js"></script>
<script>
  SlidingMarker.initializeGlobally();
</script>

That's it. Now all marker movements will be animated, e.g. :

var marker = new google.maps.Marker();
...
marker.setPosition(latLng); //Will cause smooth animation

API

SlidingMarker object that inherits google.maps.Marker is created in global scope (on injected with AMD), with following method in it:

SlidingMarker.initializeGlobally() when called, replaces google.maps.Marker with SlidingMarker, so all markers of google maps will be animated.

SlidingMarker has all the events, methods and options of google.maps.Marker - that's its beauty.

  • getPosition() function returns discrete position, instead of animated one. That means that when one calls setPosition() and animation is in place, getPosition() will return final position instead of intermediate. Use getAnimationPosition() to retrieve intermediate position.

  • position_changed event will be raised one time per call to setPosition(), even when animation is in place. To receive updates on animated movement, use animationposition_changed event.

In addition, following options are supported to fine-tune animation for each marker:

  • easing - "easeInOutQuint" by default. Possible values are same as in jquery.easing library.
  • duration - in ms, 1000 by default.
  • animateFunctionAdapter - by default, SlidingMarker assumes that google.maps.Marker.prototype is enhanced with animateTo method, but you can provide alternative animation adapter. It should be function with following declaration: animateFunctionAdapter(marker, destPosition, easing, duration). If destPosition provided is null, adapter should stop animation.

Example:

var marker = new SlidingMarker({
    position: myLatlng,
    map: map,
    title: "I'm sliding marker",
    duration: 2000,
    easing: "easeOutExpo"
});
...
// use marker as ordinal google map's marker.
// few animation customizations are also possible.
marker.setDuration(1000);

There are few additions to SlidingMarker instance:

setDuration(duration) sets duration of animation for marker, in ms.

getDuration() returns duration of animation.

setEasing(easing) sets easing type of animation.

getEasing() return easing type.

getAnimationPosition() returns position of animated marker.

setPositionNotAnimated(position) performs original not animated setPosition() on marker.

animationposition_changed event is raised when position of visible animated marker changes.

Demo

Demos reside in demo folder.

In following demo you can see that position_changed is called in natural way. Click any point on map to see marker move.

alt unobtrusive

You can use SlidingMarker with other libraries that enhance original marker, like MarkerWithLabel or Geolocation Marker.

It can be used with OverlappingMarkerSpiderfier as well:

alt unobtrusive

Here is animated MarkerWithLabel for a first time:

alt unobtrusive

Here is animated version of Geolocation Marker:

alt unobtrusive

It can be used with InfoWindow:

alt unobtrusive

This demonstrates MarkerWithGhost. Click on map to call setPosition. Right-click to call setGhostPosition. Only the "ghost" will move, causing no events.

alt unobtrusive

Sometimes libraries should be slightly modified to make use of animation. Animated versions of MarkerWithLabel and Geolocation Marker are in vendor folder.

AMD

marker-animate-unobtrusive can be used with requirejs.

'jquery' library should be configured with requirejs. marker-animate should be configured as well.

requirejs.config({
  shim: {
    "jquery-easing": ["jquery"],
    "marker-animate": { deps: ["jquery-easing"] }
  }
});

The usage is simple:

define(['SlidingMarker'], function (SlidingMarker) {
	//Use it here
}

MarkerWithGhost

In addition to SlidingMarker, you can use MarkerWithGhost class. It useful in less scenarios. Example scenario is usage with OverlappingMarkerSpiderfier.

MarkerWithGhost provides "ghost" mode. When this mode active, the marker moves as usual, but fires no position_changed events. It's position returned by getPosition method will not change either.

Ghost mode is activated by calling setGhostPosition method. It will deactivate as soon as you call to plain setPosition().

Ghost will move always and provide ghostPosition property, as well as ghostposition_changed event. When not in "ghost mode", it will move with marker. When "ghost mode" is active, the ghost will move instead of marker.

API of MarkerWithGhost

MarkerWithGhost inherits SlidingMarker.

MarkerWithGhost.initializeGlobally() works just as same method of SlidingMarker.

instance methods

setGhostPosition(ghostPosition) starts "ghost mode". Marker moves to provided position, but will not fire position_changed event, and it's position property will not change. To exit "ghost" mode call setPosition().

getGhostPosition() returns position of ghost. If not in ghost mode, it will be equal to position property.

getGhostAnimationPosition() return animation position of ghost. If not in ghost mode, it will be equal to animationPosition property.

events

ghostposition_changed fires as position of ghost changes. If not in ghost mode, it still will fire.

ghostanimationposition_changed fires as animationPosition of ghost changes. If not in ghost mode, it still will fire.

Integration with OverlappingMarkerSpiderfier

It's possible to add animation to glorious OverlappingMarkerSpiderfier project by George MacKerron. You can see a demo here, just click a bunch of markers to see them move:

alt unobtrusive

Declaimer: I think this demo is one of few things one can stare are indefinitely, along with classic Windows Defragmenter and Mac minimize animation :)

To achieve this, slightly modified version of oms.js was used that you can find in vendor folder, or in this github fork.

For curious ones, the trick to make OverlappingMarkerSpiderfier support animation is to use MarkerWithGhost instead of plain google.maps.Marker, and make oms.js call ghosted properties, e.g. to getGhostPosition() instead of getPosition().

Additional bonus arises from using MarkerWithGhost, and it is that no unnecessary position_changed events are triggered when spiderfying occurs. For you, getPostion on your marker will return its actual position, and will not be effected by spiderfier.

Under the hood

Imperfection of marker-animate

Here's demonstration of difficulties we met with original marker-animate library.

  • With it you should call animateTo() method instead of usual setPosition(). We wanted fire-and-forget functionality, so all markers will just animate.
  • Huge issue is that original library causes masses of position_changed event while marker is being animated. You can see it in this demo (of original library), with large amount of position_changed event occur:

alt obtrusive

  • While marker moves in original library, it reports its intermediate position when getPosition() called. If we call marker.setPosition(point), we want to receive the point from marker.getPosition(), and not some useless animation state.

These facts prevents marker-animate from usage with other libraries that depend on markers.

Architecture of SlidingMarker

The solution was to use decorator pattern. SlidingMarker inherits google.maps.Marker, so it can be used anywhere just like google.maps.Marker itself. For example, following is true:

SlidingMarker instanceof google.maps.Marker // true 

But this marker is never attached to the map, even if you call marker.setMap(map). Instead, there's another visible marker, called _instance, that is attached to the map. You work directly with marker, and every operation you perform on marker is redirected to _instance. For example, getMap() method called on marker will call getMap() on _instance .

Every user event, like click, that triggers on visible _instance marker is redirected back to marker itself.

This way animation that occur on visible _instance does not interfere with invisible marker that you work with.

So, storing SlidingMarker takes *2 memory than storing instance of google.maps.Marker.

Things to consider for future versions

  • ~~ Make it possible to use any easing library, not just jquery_easing ~~
  • Make it possible to restore google maps to original state after initializeGlobally() called.
  • Make it possible to stop animation. Animation should stop as well when setPositionNotAnimated is called.
  • Improve speed.
  • Provide support for alternative animation engine, e.g. Velocity.
  • Compile with closure-compiler. Annotations are already provided at annotations folder.

Contribute

Use GitHub issues and Pull Requests.

Ideas are welcome as well :)

To build:

npm run build

Before committing, please run jshint:

npm run jshint

To run tests:

npm run test

Tests reside in tests/spec folder.

Tests are also runnable in browser and debuggable with tests/SpecRunner.html.