Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

Vue.js & TypeScript で Google Mapの自作コンポーネント作成

背景

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>

表示例

配置したMarkerクリック後のスクリーンショットです。
スクリーンショット 2019-10-24 13.52.21.png

GoogleMapOverlayView

Google 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>

参考

https://github.com/laurencedorman/google-maps-api-loader

berry99
主に実装メモ保存用
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away