とある開発している中で、Vue.jsでGoogleMapを表示させる必要があったので
その時作ったコンポーネントとその使い方の一部をご紹介したいと思います。
使用技術
- Vue.js(2.6.10)
- TypeScript (3.8.3)
- Vue Property Decorator (8.1.0)
自作した理由
vue-google-mapsなどの既存ライブラリを使う選択肢もありましたが、
今後色々とカスタマイズ加えていきたいと考えた時に少々柔軟性にかけると感じ、
自由にカスタマイズしていきたいと思ったので今回は自作することにしました。
コンポーネントを作ってみる
必要最低限Mapを表示できる状態のコンポーネントにいくつかカスタマイズを抜粋して追加してみた例を紹介します。
Props
props | type | 用途 |
---|---|---|
zoom | number | mapのズームレベルを指定 |
center | { lat: number, lng: number } | map中心の緯度経度を指定 |
id | string | googlemapを表示する親NodeのID |
clickableIcons | boolean | 地図上に存在するPOIアイコンをクリックできるか否かを指定するオプション |
gestureHandling | 'auto' | 'greedy' | 'cooperative' | 'none' | スクロール時の挙動を決める |
Events
events | 用途 |
---|---|
change:bounds | 地図移動した時に、表示範囲の緯度経度情報を返却する |
GoogleMap表示コンポーネント全体
<template> <div> <div :id="id" :class="staticClass" :style="staticStyle" /> <template v-if="google && map"> <slot :google="google" :map="map" /> </template> <div v-show="loadError"> 地図読み込みに失敗しました </div> </div> </template> <script lang="ts"> import { Component, Vue, Prop, Watch } from 'vue-property-decorator' import qs from 'qs' const params = { key: 'YourGoogleMapAPIKey', libraries: 'geometry,drawing,places', callback: 'handleLoadGoogleMapsScript' } interface GoogleMapWindow extends Window { handleLoadGoogleMapsScript: Function google: any } declare const window: GoogleMapWindow const MAP_SCRIPT_ID = 'google-map-script' const MAX_RETRY_LOAD_SCRIPT = 10 const LOAD_SCRIPT_INTERVAL = 500 @Component export default class GoogleMap extends Vue { @Prop({ default: 17 }) private zoom!: number @Prop() private center!: { lat: number; lng: number } @Prop({ default: 'googleMap' }) private id!: string @Prop({ default: false }) private clickableIcons!: boolean @Prop({ default: 'auto' }) private gestureHandling!: | 'auto' | 'cooperative' | 'none' | 'greedy' google: any = null map: any = null loadError: boolean = false get staticClass() { return this.$vnode.data ? this.$vnode.data.staticClass : '' } get staticStyle() { return this.$vnode.data ? this.$vnode.data.staticStyle : '' } @Watch('center.lat') @Watch('center.lng') updatedCenter() { this.map && this.map.panTo(this.center) } mounted() { this.loadGoogleMapsScript() .then(google => { this.google = google this.initializeMap() }) .catch(_e => { if (this.map) return this.loadError = true }) } loadGoogleMapsScript() { return new Promise(async (resolve, reject) => { if (window.google) { return resolve(window.google) } const script = document.createElement('script') script.id = MAP_SCRIPT_ID script.src = `https://maps.googleapis.com/maps/api/js?${qs.stringify( params )}` const head = document.querySelector('head') if (!head) return reject(new Error('head node is undefined')) if (!document.getElementById(MAP_SCRIPT_ID)) { head.appendChild(script) window.handleLoadGoogleMapsScript = () => { resolve(window.google) } } for (const retryCount of range(1, MAX_RETRY_LOAD_SCRIPT)) { if (window.google) { resolve(window.google) break } await sleep(LOAD_SCRIPT_INTERVAL) if (retryCount === MAX_RETRY_LOAD_SCRIPT) { reject(new Error('failed load google api')) } } }) } initializeMap() { const mapContainer = this.$el.querySelector(`#${this.id}`) const { Map, MapTypeId } = this.google.maps this.map = new Map(mapContainer, { zoom: this.zoom, center: this.center, mapTypeId: MapTypeId.ROADMAP, clickableIcons: this.clickableIcons, gestureHandling: this.gestureHandling }) this.map.addListener('bounds_changed', () => { this.$emit('change:bounds', this.buildBounds()) }) } buildBounds() { const bounds = this.map.getBounds() const swLatlng = bounds.getSouthWest() const swLat = swLatlng.lat() const swLng = swLatlng.lng() const neLatlng = bounds.getNorthEast() const neLat = neLatlng.lat() const neLng = neLatlng.lng() return { swLat, swLng, neLat, neLng } } } </script>
GoogleMapコンポーネント利用例
<template> <GoogleMap :zoom="16" :center="{ lat, lng }" gestureHandling="greedy" @change:bounds="handleChangeBounds" style="width: 600px; height: 600px;" /> </template> <script lang="ts"> import { Component, Vue } from 'vue-property-decorator' import GoogleMap from 'path/to/GoogleMap.vue' @Component({ components: { GoogleMap } }) export default class Example extends Vue { lat: number = 35.6695939 lng: number = 139.7617964 handleChangeBounds(bounds: { swLat: number, swLng: numebr, neLat: number, neLng: number }) { console.log(bounds) } } </script>
感想
自作で色んな用途に特化したコンポーネントを作成して、いかに使いやすく便利にできるかを考察するのは楽しいですし、自由にカスタマイズもできるのでメリットも大きいと感じました。
機会があればその他GoogleMapの機能についてもご紹介できればなと思います。