-
Notifications
You must be signed in to change notification settings - Fork 89
feat!: improve google maps integration #191
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9b3ea9f
5c4ce1e
da0df4e
a4dd5ff
a2a9319
55de5b0
82d3659
28cc189
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,8 +34,8 @@ You'll need an API key with permissions to access the [Static Maps API](https:// | |
|
|
||
| Showing an interactive JS map requires the Maps JavaScript API, which is a paid service. If a user interacts with the map, the following costs will be incurred: | ||
| - $7 per 1000 loads for the Maps JavaScript API (default for using Google Maps) | ||
| - $5 per 1000 loads for the Geocoding API | ||
| - $2 per 1000 loads for the Static Maps API | ||
| - $2 per 1000 loads for the Static Maps API - You can avoid providing a `placeholder` slot. | ||
| - $5 per 1000 loads for the Geocoding API - You can avoid this by providing a `google.maps.LatLng` object instead of a string for the `center` prop | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: can we link to the source, where the amounts are coming from? |
||
|
|
||
| However, if the user never engages with the map, only the Static Maps API usage ($2 per 1000 loads) will be charged. | ||
|
|
||
|
|
@@ -53,45 +53,73 @@ and are okay with a less interactive map. | |
| ```vue [Input] | ||
| <script setup lang="ts"> | ||
| import { ref } from 'vue' | ||
| import type { Ref } from 'vue' | ||
|
|
||
| const isLoaded = ref(false) | ||
| const center = ref() | ||
| const maps = ref() | ||
|
|
||
| const query = ref('Space+Needle,Seattle+WA') | ||
| function handleReady(_map: Ref<google.maps.Map>) { | ||
| const map = _map.value | ||
| center.value = map.getCenter() | ||
| map.addListener('center_changed', () => { | ||
| center.value = map.getCenter() | ||
| const query = ref({ | ||
| lat: -37.7995487, | ||
| lng: 144.9867841, | ||
| }) | ||
|
|
||
| const markers = ref([]) | ||
|
|
||
| let increment = 1 | ||
| function addMarker() { | ||
| // push to markers, we want to add a marker from the center but randomize the position by a bit | ||
| const _center = center.value || query.value | ||
| // lat and lng may be a function | ||
| const _lat = typeof _center.lat === 'function' ? _center.lat() : _center.lat | ||
| const _lng = typeof _center.lng === 'function' ? _center.lng() : _center.lng | ||
| const lat = (1000 * _lat + increment) / 1000 | ||
| const lng = (1000 * _lng + increment) / 1000 | ||
| increment += 1 | ||
|
|
||
| markers.value.push(`${lat},${lng}`) | ||
| } | ||
|
|
||
| function removeMarkers() { | ||
| markers.value = [] | ||
| increment = 1 | ||
| } | ||
| function handleReady({ map }) { | ||
| center.value = map.value.getCenter() | ||
| map.value.addListener('center_changed', () => { | ||
| center.value = map.value.getCenter() | ||
| }) | ||
| isLoaded.value = true | ||
| } | ||
| </script> | ||
|
|
||
| <template> | ||
| <div class="not-prose"> | ||
| <div class="flex items-center justify-center p-5"> | ||
| <ScriptGoogleMaps | ||
| ref="maps" | ||
| :query="query" | ||
| api-key="AIzaSyAOEIQ_xOdLx2dNwnFMzyJoswwvPCTcGzU" | ||
| width="600" | ||
| height="400" | ||
| class="group" | ||
| @ready="handleReady" | ||
| /> | ||
| </div> | ||
| <div class="text-center"> | ||
| <UAlert v-if="!isLoaded" class="mb-5" size="sm" color="blue" variant="soft" title="Hover to load" description="Hover the map will load the Google Maps iframe." /> | ||
| <UAlert v-if="isLoaded" class="mb-5" size="sm" color="blue" variant="soft"> | ||
| <template #title> | ||
| Center: {{ center }} | ||
| </template> | ||
| </UAlert> | ||
| </div> | ||
| <div class="not-prose"> | ||
| <div class="flex items-center justify-center p-5"> | ||
| <ScriptGoogleMaps | ||
| ref="maps" | ||
| :center="query" | ||
| :markers="markers" | ||
| api-key="AIzaSyAOEIQ_xOdLx2dNwnFMzyJoswwvPCTcGzU" | ||
| class="group" | ||
| above-the-fold | ||
| @ready="handleReady" | ||
| /> | ||
| </div> | ||
| <div class="text-center"> | ||
| <UAlert v-if="!isLoaded" class="mb-5" size="sm" color="blue" variant="soft" title="Static Image: Hover to load interactive" description="Hovering the map will trigger the Google Maps script to load and init the map." /> | ||
| <UAlert v-if="isLoaded" class="mb-5" size="sm" color="blue" variant="soft" title="Interactive Map"> | ||
| <template #description> | ||
| Center: {{ center }} | ||
| </template> | ||
| </UAlert> | ||
| <UButton @click="addMarker" type="button" class=""> | ||
| Add Marker | ||
| </UButton> | ||
| <UButton v-if="markers.length" @click="removeMarkers" type="button" color="gray" variant="ghost" class=""> | ||
| Remove Markers | ||
| </UButton> | ||
| </div> | ||
| </div> | ||
| </template> | ||
| ``` | ||
|
|
||
|
|
@@ -101,16 +129,43 @@ function handleReady(_map: Ref<google.maps.Map>) { | |
|
|
||
| The `ScriptGoogleMaps` component accepts the following props: | ||
|
|
||
| - `trigger`: The trigger event to load the Google Maps. Default is `mouseover`. See [Element Event Triggers](/docs/guides/script-triggers#element-event-triggers) for more information. | ||
| - `aboveTheFold`: Optimizes the placeholder image for above-the-fold content. Default is `false`. | ||
| **Map** | ||
|
|
||
| - `center`: Where to center the map. You can provide a string with the location or use a `{ lat: 0, lng: 0 }` object. | ||
| - `apiKey`: The Google Maps API key. Must have access to the Static Maps API as well. You can optionally provide this as runtime config using the `public.scripts.googleMaps.apiKey` key. | ||
| - `query`: Map marker location. You can provide a string with the location or use the `google.maps.LatLng` object. | ||
| - `options`: Options for the map. See [MapOptions](https://developers.google.com/maps/documentation/javascript/reference/map#MapOptions). | ||
| - `width`: The width of the map. Default is `600`. | ||
| - `height`: The height of the map. Default is `400`. | ||
| - `centerMarker`: Whether to display a marker at the center position. Default is `true`. | ||
| - `mapOptions`: Options for the map. See [MapOptions](https://developers.google.com/maps/documentation/javascript/reference/map#MapOptions). | ||
|
|
||
| **Placeholder** | ||
|
|
||
| You can customize the placeholder image using the following props, alternatively, you can use the `#placeholder` slot to customize the placeholder image. | ||
|
|
||
| - `placeholderOptions`: Customize the placeholder image attributes. See [Static Maps API](https://developers.google.com/maps/documentation/maps-static/start). | ||
| - `placeholderAttrs`: Customize the placeholder image attributes. | ||
|
|
||
| **Sizing** | ||
|
|
||
| If you want to render a map larger than 640x640 you should provide your own placeholder as the [Static Maps API](https://developers.google.com/maps/documentation/maps-static/start) | ||
| does not support rendering maps larger than this. | ||
|
|
||
| - `width`: The width of the map. Default is `640`. | ||
| - `height`: The height of the map. Default is `400`. | ||
|
|
||
| **Optimizations** | ||
|
|
||
| - `trigger`: The trigger event to load the Google Maps. Default is `mouseover`. See [Element Event Triggers](/docs/guides/script-triggers#element-event-triggers) for more information. | ||
| - `aboveTheFold`: Optimizes the placeholder image for above-the-fold content. Default is `false`. | ||
|
|
||
| **Markers** | ||
|
|
||
| You can add markers to the static and interactive map by providing an array of `MarkerOptions`. See [MarkerOptions](https://developers.google.com/maps/documentation/javascript/reference/marker#MarkerOptions). | ||
|
|
||
| - `markers`: An array of markers to display on the map. | ||
|
|
||
| See the [markers](https://github.com/nuxt/scripts/blob/main/playground/pages/third-parties/google-maps/markers.vue) example for more information. | ||
|
|
||
| ### Guides | ||
|
|
||
| #### Eager Loading Placeholder | ||
|
|
||
| The Google Maps placeholder image is lazy-loaded by default. You should change this behavior if your map is above the fold | ||
|
|
@@ -132,6 +187,57 @@ or consider using the `#placeholder` slot to customize the placeholder image. | |
|
|
||
| :: | ||
|
|
||
| #### Advanced Marker Control | ||
|
|
||
| If you need more control over the markers on the map, you can use the exposed `createAdvancedMapMarker` function which | ||
| will return the marker instance. | ||
|
|
||
| ```vue | ||
| <script lang="ts" setup> | ||
| const googleMapsRef = ref() | ||
| onMounted(() => { | ||
| const marker = googleMapsRef.value.createAdvancedMapMarker({ | ||
| position: { } | ||
| }) | ||
| }) | ||
| </script> | ||
| <template> | ||
| <ScriptGoogleMaps ref="googleMapsRef" /> | ||
| </template> | ||
| ``` | ||
|
|
||
|
|
||
| #### Advanced Map Control | ||
|
|
||
| The component exposes all internal APIs, so you can customize your map as needed. | ||
|
|
||
| ```vue | ||
| <script lang="ts" setup> | ||
| const googleMapsRef = ref() | ||
| onMounted(async () => { | ||
| const api = googleMapsRef.value | ||
|
|
||
| // Access internal APIs | ||
| const googleMaps = api.googleMaps.value // google.maps api | ||
| const mapInstance = api.map.value // google.maps.Map instance | ||
|
|
||
| // Convert a query to lat/lng | ||
| const query = await api.resolveQueryToLatLang('Space Needle, Seattle, WA') // { lat: 0, lng: 0 } | ||
|
|
||
| // Import a Google Maps library | ||
| const geometry = await api.importLibrary('geometry') | ||
| const distance = new googleMaps.geometry.spherical.computeDistanceBetween( | ||
| new googleMaps.LatLng(0, 0), | ||
| new googleMaps.LatLng(0, 0) | ||
| ) | ||
| }) | ||
| </script> | ||
| <template> | ||
| <ScriptGoogleMaps ref="googleMapsRef" /> | ||
| </template> | ||
| ``` | ||
|
|
||
|
|
||
| ### Component API | ||
|
|
||
| See the [Facade Component API](/docs/guides/facade-components#facade-components-api) for full props, events, and slots. | ||
|
|
@@ -150,7 +256,7 @@ To subscribe to Google Map events, you can use the `ready` event. | |
|
|
||
| ```vue | ||
| <script setup lang="ts"> | ||
| function handleReady(map) { | ||
| function handleReady({ map }) { | ||
| map.addListener('center_changed', () => { | ||
| console.log('Center changed', map.getCenter()) | ||
| }) | ||
|
|
@@ -256,14 +362,23 @@ Loading the Google Maps SDK and interacting with it programmatically. | |
|
|
||
| ```vue | ||
| <script setup lang="ts"> | ||
| /// <reference types="google.maps" /> | ||
| const { $script } = useScriptGoogleMaps({ | ||
| apiKey: 'key' | ||
| }) | ||
| $script.then(({ maps }) => { | ||
| const map = new maps.Map(document.getElementById('map'), { | ||
| center: { lat: -34.397, lng: 150.644 }, | ||
| zoom: 8 | ||
| const map = ref() | ||
| onMounted(() => { | ||
| $script.then(async (instance) => { | ||
| const maps = await instance.maps as any as typeof google.maps // upstream google type issue | ||
| new maps.Map(map.value, { | ||
| center: { lat: -34.397, lng: 150.644 }, | ||
| zoom: 8 | ||
| }) | ||
| // Do something with the map | ||
| }) | ||
| }) | ||
| </script> | ||
| <template> | ||
| <div ref="map" /> | ||
| </template> | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| <script setup> | ||
| import { ref } from 'vue' | ||
|
|
||
| const mapOptions = ref({ | ||
| center: { lat: -34.397, lng: 150.644 }, | ||
| }) | ||
| function changeQuery() { | ||
| mapOptions.value = { | ||
| center: { | ||
| // move just a little | ||
|
harlan-zw marked this conversation as resolved.
|
||
| lat: mapOptions.value.center.lat + 0.01, | ||
| lng: mapOptions.value.center.lng + 0.01, | ||
| }, | ||
| } | ||
| } | ||
| </script> | ||
|
|
||
| <template> | ||
| <div> | ||
| <div> | ||
| <ScriptGoogleMaps | ||
| ref="googleMapsRef" | ||
| api-key="AIzaSyAOEIQ_xOdLx2dNwnFMzyJoswwvPCTcGzU" | ||
| :width="1200" | ||
| :height="600" | ||
| :map-options="mapOptions" | ||
| above-the-fold | ||
| @init="setupGoogleMaps" | ||
| /> | ||
| </div> | ||
| <div class="button-container"> | ||
| <button | ||
| class="button" | ||
| @click="changeQuery" | ||
| > | ||
| move center | ||
| </button> | ||
| </div> | ||
| </div> | ||
| </template> | ||
|
|
||
| <style> | ||
| .button-container { | ||
| margin: 20px 0; | ||
| } | ||
|
|
||
| .button { | ||
| background-color: orange; | ||
| border-radius: 8px; | ||
| padding: 4px 8px; | ||
| cursor: pointer; | ||
| } | ||
|
|
||
| .button:not(:last-child) { | ||
| margin-right: 8px; | ||
| } | ||
| </style> | ||
Uh oh!
There was an error while loading. Please reload this page.