背景
Vue.jsでGoogle Mapを表示する際に、既存のライブラリであるvue2-google-mapsを使う選択肢もありましたが、今後様々なカスタマイズをしていきたいと考えた場合に使い勝手が少し悪いため自作することにしました。
今回の実装では現状私が必要とする機能のみなので、必要な場合は追加で実装する必要があります。
実装
GoogleMap
Google Map表示用のコンポーネントです。
GoogleMap.vue
<template>
<div>
<div id="googleMap" :style="`height:${height}`"></div>
<template v-if="google && map">
<slot :google="google" :map="map" />
</template>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator'
import qs from 'query-string'
const params = {
key: 'Google API key',
libraries: 'geometry,drawing,places',
callback: 'handleLoadGoogleMapsScript'
}
interface GoogleMapWindow extends Window {
handleLoadGoogleMapsScript: Function
google: any
}
declare const window: GoogleMapWindow
@Component
export default class GoogleMap extends Vue {
@Prop() private zoom!: number
@Prop() private center!: { lat: number; lng: number }
@Prop({ default: '240px' }) private height!: string
google: any = null
map: any = null
mounted() {
this.loadGoogleMapsScript().then(google => {
this.google = google
this.initializeMap()
})
}
loadGoogleMapsScript() {
return new Promise((resolve, reject) => {
if (window.google) {
return resolve(window.google)
}
const script = document.createElement('script')
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'))
head.appendChild(script)
window.handleLoadGoogleMapsScript = () => {
resolve(window.google)
}
setTimeout(() => {
if (!window.google) {
reject(new Error('failed load google api'))
}
}, 5000)
})
}
initializeMap() {
const mapContainer = this.$el.querySelector('#googleMap')
const { Map, MapTypeId } = this.google.maps
this.map = new Map(mapContainer, {
zoom: this.zoom,
center: this.center,
mapTypeId: MapTypeId.ROADMAP
})
}
}
</script>
GoogleMapMarker
Goggle Map上に配置するMarker用のコンポーネントです。
追加機能として、クリックされた際に情報を表示させます。
GoogleMapMarker.vue
<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator'
@Component
export default class GoogleMapMarker extends Vue {
@Prop() private google!: any
@Prop() private map!: any
@Prop() private position!: { lat: number; lng: number }
@Prop() private title!: string
marker: any
infoWindow: any
mounted() {
const { Marker } = this.google.maps
this.marker = new Marker({
position: this.position,
map: this.map
})
this.infoWindow = new this.google.maps.InfoWindow({
content: `<div>${this.title}<div>`
})
this.google.maps.event.addListener(this.marker, 'click', () => {
this.infoWindow.open(this.map, this.marker)
})
}
}
</script>
使用例
<GoogleMap
:zoom="17"
:center="center"
:height="240"
>
<template slot-scope="scope">
<GoogleMapMarker
v-for="(marker, i) in markers"
:key="i"
:position="marker"
:google="scope.google"
:map="scope.map"
/>
</template>
</GoogleMap>
表示例
参考