LoginSignup
22
16

More than 5 years have passed since last update.

【Vue.js】単一ファイルコンポーネントでGoogleマップのマーカークラスタリングとvue-router

Last updated at Posted at 2017-12-14

Googleマップ表示とマーカークラスタリングと情報ウインドウからのページ遷移

単一ファイルコンポーネントでGoogleマップを表示して、複数マーカーをまとめて表示(マーカークラスタリング)したうえで、情報ウインドウ内から非同期でページ遷移する(vue-router)

Vueの単一ファイルコンポーネントでGoogleMapsAPIを利用した地図を表示します。
npmに vue2-google-maps というのがあるので、単純な地図ならこれでよいのですが
今回は

等々ありましたので、自前でコンポーネントを作成しました。
それについてのメモです。

環境

  • サーバサイド:Laravel5.5
  • フロントエンド:Vue.js
  • 単一ファイルコンポーネントのコンパイル:Laravel Mix

準備

GoogleMapの読み込み

<script src="https://maps.googleapis.com/maps/api/js?key=[API_KEY]"></script>

いつものGoogleMapAPIのやつです。

マーカークラスタリング

マーカーを沢山表示するときに、近くのマーカーが重なって見えにくくなってしまうのを回避する手段です。
詳しくは
MarkerClusterer

npm からインストール

$ npm i js-marker-clusterer

ライブラリを読み込んでおきます。

app.js
import MarkerClusterer from 'js-marker-clusterer';

前提条件

  • GoogleマップのinfoWindow(マーカーから出る吹き出しみたいなやつ)から、リンクを貼って、詳細ページへ遷移させたい。
  • 詳細ページのURLは /999 みたいな詳細を表すID

vue-routerを使って事前にルーティング

app.js
const router = new VueRouter({
  mode: 'history',
  routes: [
    {
      path: '/',
      name:'home',
      component: require('./components/Home.vue')
    },
    {
      path: '/:post_id(\\d+)',
      name: 'description',
      component: require('./components/Description.vue')
    }
    // ↑こういう感じでIDをURLにした詳細ページ的なのをつくると仮定して進めます
  ]
});

こんな感じでルーティングしています。

コンポーネントファイル

Map.vue
<template lang="html">
  <div>
    <!-- テンプレートは単純にマップだけ -->
    <div id="map"></div>
  </div>
</template>

<script>
export default {
  data: function () {
    return {
      mapName: "map", //mapを紐付ける要素のID
      map: null, //GoogleMapのインスタンスを入れる変数
      markers: [], //マーカー群を入れる配列

      // サンプルデータです。指している場所には意味はありません。
      // ロケーション(緯度・軽度)と、id、タイトルです。
      locations: [
        {
          location:{lat: 35.681, lng: 139.761},
          id:1,
          title:"たいとる1"
        },
        {
          location:{lat: 35.682, lng: 139.762},
          id:2,
          title:"たいとる2"
        },
        {
          location:{lat: 35.683, lng: 139.763},
          id:3,
          title:"たいとる3"
        },
        {
          location:{lat: 35.684, lng: 139.764},
          id:4,
          title:"たいとる4"
        },
        {
          location:{lat: 35.685, lng: 139.765},
          id:5,
          title:"たいとる5"
        }

      ],
    }
  },
  methods: {
    // マップを表示するメソッド
    initMap: function () {
      var my_this = this; //thisを別名で退避しておく(あとでつかうので)

      // 中心データ
      var center_lat = 35.6810409;
      var center_lng = 139.7670516;
      var zoom = 16;

      // マップを初期化
      this.map = new google.maps.Map(document.getElementById(this.mapName), {
        center: {lat:center_lat, lng: center_lng},
        zoom: zoom
      });

      // this.locations をループしてマーカー群を作成
      this.markers = this.locations.map( function(data, i) {
        var marker = new google.maps.Marker({
          position: data.location,
        });

        // 情報ウインドウ(infoWindow)を作成 idとデータのID番号を設定しておく
        var infowindow = new google.maps.InfoWindow({
          content: '<div id="infobox_' + data.id + '" data-item-id="'+ data.id +'">' + data.title + '</div>',
          maxWidth: 300
        });

        // 情報ウインドウ(infoWindow)にイベントを設定
        // 情報ウインドウにclickイベントが設置できなさそうなので、読み込み時に、中身にクリックイベントを設置する
        google.maps.event.addListener(infowindow, 'domready', function(){

          //infoWindowが持っている内部文字列をDOMに変換
          var dumy_dom =  document.createElement('div');//ダミーの要素を作成
          dumy_dom.innerHTML = this.content;//ダミー要素にinfoWindow内のHTML文字列を読み込ませてDOMに変換

          // 内部のDOMを取得
          var id_name = dumy_dom.children[0].id; //infoWindow内の要素のID名を取得
          var inner_dom = document.getElementById(id_name); //ID名からinfoWindow内の要素オブジェクトを取得

          // アイテムのid番号を取得
          var item_id = inner_dom.dataset.itemId; //同じくリンク先用の番号を取得(data-item-idの値)

          // infoWindow内の要素にクリックイベントを設置
          inner_dom.addEventListener('click', function(){
            my_this.go(item_id);
          });
        });

        // infoWindowをマーカーに紐付け
        marker.addListener('click', function() {
          infowindow.open(this.map, marker);
        });
        return marker;
      });

      // マーカークラスタ設定
      var markerCluster = new MarkerClusterer(this.map, this.markers,
        {imagePath: '/images/markerclusterer/m'} // マーカークラスタ画像のパス(予めDLしておく)
      );
    },

    // Vue-routeをつかって非同期にページ遷移させるメソッド
    go: function(item_id){
      this.$router.push({ name: 'description', params: { post_id: item_id }});
    }
  },
  mounted: function () {
    this.initMap();
  }
}
</script>

<style lang="css">
#map {
  width:100%;
  height:400px;
}
</style>

ポイント(というか詰まったところ)

  • infoWindow内の要素からリンクさせたかったのですが、infoWindowのcontentで、vue-routerを使う方法がわかりませんでした。なので、infoWindowの読み込みイベントから、内部DOMを取得してクリックイベントを設定する。という強引な方法で実現しました。
  • たぶん、もっとスマートなVueっぽい方法があるのではないかと思案中。
  • マーカークラスタ用の画像については、下記からダウンロードできますが、画像ファイルなだけなので、自作するとよいかも。
  • そこまでしてSPAにこだわらなくても・・というのは言わないで。

 参考

22
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
16