Google Maps API v3で任意のDOMをマーカーにしてアニメーションで移動させたい

なぜかGoogle Maps APIを使うことがここ最近は増えました。

さて、Google Maps API v3を使って地図上にマーカーを表示させる場合、標準APIだけだと少し不足感を感じることがあります。
まず、標準APIで使えるのはデフォルトのピン(画像データ)や、指定した画像、SVG Path記法などに限られます。
また、アニメーションによってマーカーを移動させることはできません。標準で用意されているマーカーアニメーションもあるにはある ( Marker Animations)のですが、指定した位置にマーカーが降ってきてバウンドする、といったものなので、アニメーションによるマーカーの移動はできません。地図自体は panTo メソッドによって移動することができるのですが、マーカーだけをアニメーションでスムーズに移動することはできません。

Google Maps APIをサポートするライブラリ

というわけで、標準APIでできないことを可能にするために提供されているライブラリについて調査。

  • Google Maps API Libraries
    • Googleが出しているライブラリ郡です。GitHubのGoogle Maps OrganizationにはJavaPython用のクライアントなんかもあります。
    • 「任意のDOMをマーカーにする」というのは、まさにrich markerで実現可能です。
    • また、MarkerWithLabelというものもあります。マーカーにラベルを表示させるものですが、labelContentプロパティには任意のDOMを指定することができます。
  • SlidingMarker
    • Google謹製ではないですが、マーカーをアニメーションさせるためのライブラリ。marker-animateをベースに、移動後にマーカーがバウンドしないように改良されたものです。アニメーション処理自体はjQuey easing(animate)で行われているみたいですね。

結論

というわけで、SlidingMarkerとMarkerWithLabelを組み合わせることにしました。なぜこの組み合わせかというと、一番の理由は、そのまんまのデモが用意されているからですw

https://github.com/terikon/marker-animate-unobtrusive/blob/master/demo/markerwithlabelmove-sliding.html

webpackでビルドできるようにして使う

ここからは蛇足。

SlidingMarkerとMarkerWithLabelを使った別の理由として、SlidingMarkerとMarkerWithLabelのどちらもnpmとして提供されているのもポイントでした。最近はwebpackを使っているので、モジュールはBowerでなくnpmで提供されているものだけに集約したいのです。まぁ、js-rich-markerはbowerで提供されており、bowerで提供されているライブラリの依存解決もwebpackならできるため(Usage with Bower)、SlidingMarker + RichMarkerの組み合わせがベストっぽいですが…。

それはさておき、webpackでビルドすることを前提に、フロント用のJSもCommonJSスタイルで開発する際の手順ですが、2つポイントがありました。

  • 【ポイント1】デモで使われている markerwithlabel は、npmで提供されているものでなく、独自にカスタマイズされたもの(markerwithlabel.terikon.js)を使わなければならないです。
    • カスタマイズ版を使わないと、マーカーはきちんとアニメーションしますがラベルはアニメーションしないので、本体のマーカーが移動後に瞬間移動する、という動きになってしまいます。
    • すなわち、結局npmで提供されているmarkerwithlabelは使えませんでした…。
  • 【ポイント2】SlidingMarkerとMarkerWithLabelも共にgoogle.maps.Markerを拡張したものであるため、google.maps.MarkerをSligindMarkerでグローバルに置き換える必要があります。

MarkerWithLabelとしてmarkerwithlabel.terikon.jsを使うようにする

webpackの設定は webpack.config.js で記述します。ポイント的なところだけを抜粋すると以下の様なコンフィグになります。

var path = require('path');

var customMarkerWithLabelPath = path.join(__dirname,
  'node_modules',
  'marker-animate-unobtrusive',
  'vendor',
  'markerwithlabel.terikon.js');

module.exports = {
  entry: {
    app: './src/main.js'
  },
  output: {
    path: path.resolve(__dirname, 'public', 'js'),
    filename: '[name].js'
  },
  module: {
    loaders: [
      { test: customMarkerWithLabelPath, loader: 'exports?MarkerWithLabel' }
    ]
  },
  resolve: {
    root: __dirname,
    alias: {
      MarkerWithLabel: customMarkerWithLabelPath
    },
    extensions: ['', '.js', '.jsx', 'es6'],
    moduleDirectories: [
      'src',
      'node_modules'
    ]
  }
};

こちらを参考にしました。

2no.hatenablog.com

google.maps.MarkerをSligindMarkerで置き換える

SligingMarker.initializeGloballyを呼び出しても、webpackが色々と良きに計らってくれているため、MarkerWithLabel側が参照するgoogle.maps.Markerは、完全にSlidingMarkerに置き換わりません。
仕方なく、メインエントリ用のjsで以下のようにしました。

require('expose?$!expose?google.maps.Marker!marker-animate-unobtrusive'),

この辺りは、かなりバッドプラクティス感が漂っている気がします…。

そんなわけで、一応やりたいことはできました。SlidingMarker + RichMarkerの組み合わせができるかどうかに続きます(多分)

ちなみに、今回確認に使ったリポジトリはこちら(READMEすら書いていないですが、npm install 後に webpack でビルド後に npm start するとと動きます):

github.com