GoogleMap のマーカーと、HTMLのリストデータの連動(Vanilla JavaScript)
※機能追加しました。
GoogleMap のマーカークリック時にアイコンが変更するようにしました。(スマホ対応注意点あり)
1. 作ったきっかけ
BMW 公式サイトのディーラー検索ページの UI がよくできているとおもったので、これを実装することにしました。
具体的には、地図上のマーカーをクリックすると、左側のディーラーリストのうち、該当の店舗のリスト情報がハイライトされる。また、店舗リストの情報をクリックすると該当のマーカーが地図上でハイライトされる(実際には BMW のマークに代わる)
よく見かける例として、地図のマーカーをクリックすると、InfoWindow(吹き出し)に情報が表示されるものがありますが、リストと地図が連動するこの形の方がよいと思い、今回の開発に至りました。
2. 作成したページ
作成したページがこちら
画面下のリストの部分と、地図上のマーカーが相互に連動します。
(英語版ですが、日本語表示の変更ももちろん可能です
3. コードの説明
ソースコードは GitHub にアップしました。特に map フォルダ以下のコードを参照してください。
<a id="markdown-31-json-データの取得" name="31-json-データの取得"></a>
3.1. json データの取得
ローカルにある json データから地図上に表示するジムのデータを取得します。
(GetData クラスの getLocation メソッド。)
json データは事前に作成しておきます。関連表示する画像データも image フォルダに保存しておきます。
fetch メソッドで、事前に定義した json ファイルのデータを取得し、map 関数を利用して、makers 配列にデータを格納し、以降の処理で利用できるようにします。
class GetData {
async getLocation() {
try {
const result = await fetch("location.json");
const data = await result.json();
let markers = data.items;
markers = markers.map((item) => {
const {
title,
latitude,
longitude,
address,
id,
category,
} = item.fields;
const image = item.fields.image.fields.file.url;
return { title, latitude, longitude, address, id, category, image };
});
return markers;
} catch (error) {
console.log(error);
}
}
}
3.2. Map の初期化
initMap メソッドで、GoogleMap を初期化します。
(初期ズーム、地図の中心等が定義されます)
// Mapの初期化
function initMap() {
const myOptions = {
zoom: 13,
center: new google.maps.LatLng(37.789096, -122.40217),
mapTypeId: google.maps.MapTypeId.ROADMAP,
};
const map = new google.maps.Map(
document.getElementById("map_canvas"),
myOptions
);
// 中略
}
3.3. リスト要素へのイベントリスナー付与
先ほど定義された marker 配列データから、地図上に表示するマーカーと li リストのデータを生成します。また、この li リストに対し、イベントリスナーを登録します。(リストをクリックすると、対応するマーカーが反応し、InfoWindow(吹き出し)が表示され、地図の中心に表示されます)
function initMap() {
// 省略
const markers = new GetData();
// marker配列データからマーカーの作成およびli要素の作成
markers
.getLocation()
.then((items) => {
items.forEach((item) => {
const id = item.id;
const name = item.title;
const latlng = new google.maps.LatLng(item.latitude, item.longitude);
const address = item.address;
const image = item.image;
createMarker(name, latlng, map, id);
maplistUl.innerHTML += `<li><img src=${image} /><div> <h4>${name}</h4> Address : ${address}</div></li>`;
});
})
// 生成されたli要素へのイベントリスナーの付与。
.then(() => {
maplistLi = document.querySelectorAll("#mapList li");
maplistLi.forEach((listItem, index) => {
listItem.addEventListener("click", () => {
google.maps.event.trigger(maplist[index], "click");
});
});
});
}
3.4. マーカーをクリックしたときの処理
今度は、マーカーをクリックしたときに InfoWindow(吹き出し)の表示と、マーカーに関連した、リストデータの背景を変更する処理を記述します
先ほどのリストのイベントリスナーの処理とこちらの処理の記述で相互に連動する、動きが実現されます。
function createMarker(name, latlng, map, id) {
const infoWindow = new google.maps.InfoWindow();
const marker = new google.maps.Marker({ position: latlng, map: map });
google.maps.event.addListener(marker, "click", function(e) {
// クリック済みのMakerに対応するliリストのCSS背景を初期化
maplistLi.forEach((item) => {
if (item.classList.contains("clicked")) {
item.classList.remove("clicked");
}
});
if (currentWindow) {
currentWindow.close();
}
infoWindow.setContent(name);
infoWindow.open(map, marker);
currentWindow = infoWindow;
map.panTo(latlng); //markerをクリックした時に地図の中心に
// クリックされたMarkerに対応するli要素のcss背景を操作する。
maplistLi[id - 1].classList.add("clicked");
});
maplist[cnt++] = marker;
}
4. 躓いた点等
4.1. jQuery から Vanilla JavaScript の違い
参考にしたサイトのコードは jQuery で書かれていました。jQuery では、DOM セレクタで取得された li 要素(NodeList)に対して、直接イベントリスナーを定義できますが、VanillaJS では、NodeList に対して、いったん forEach で個別の要素に展開してから、それぞれの要素にイベントリスナーを登録する必要があります。
4.1.1. jQuery のコード
//***** JQuery Sample *****
$(function() {
$("#mapList li").click(function() {
var no = $("#mapList li").index(this);
google.maps.event.trigger(maplist[no], "click");
});
});
4.1.2. VanillaJS のコード
// ***** VanillaJS で書き換え*****
document.addEventListener("DOMContentLoaded", function() {
maplistLi.forEach((listItem, index) => {
listItem.addEventListener("click", () => {
google.maps.event.trigger(maplist[index], "click");
});
});
});
※ES6 になり、jQuery でできることは、Vanilla JavaScript でもほぼ同様にできるようになりましたが、微妙に違う点もあるので注意が必要です。
4.2. Map 表示のレスポンシブ対応
GoogleMaps のサンプルコードの多くは、サイズが height と width が 400px,600px 等で固定値で設定されているが、スマホでの見た目等も考慮して、レスポンシブ対応にしました。
HTML は .mapContainer > .mapComponent > #map_canvas の 3 階層構成
それぞれの要素に対して、以下の CSS を付与しました。
.mapContainer {
width: calc(100% - 20px);
margin: 100px auto 80px;
}
.mapComponent {
position: relative;
width: 100%;
margin: 20px auto 50px;
padding-top: 65%;
}
#map_canvas {
position: absolute;
top: 0;
margin: 20px auto 10px;
width: 100%;
height: 100%;
box-shadow: 10px 10px 10px rgba (190, 190, 190, 0.4);
}
5. 利用にあたっての手順
-
maps フォルダのデータをコピーして、
-
GoogleMapsAPIKey の取得と API 利用制限の設定
-
HTML ファイルでの script 読み込み設定
(API キーを自分で取得したものに、書き換えてください)
以下リンクの手順を参考に対応することができます。
https://www.zenrin-datacom.net/business/gmapsapi/api_key/index.html -
アセットファイル(location.json と images ファイルの変更)
サンプルでは 5 か所のデータですが、増やすことも減らすことも可能です。 -
HTML ファイル、css ファイルの変更
必要に応じて適宜変更してください。
5.1. 今後の発展
getLocation メソッドの fetch 関数の引数あたりを変更することで、ローカルの json ファイルだけでなく、データベース API との連携も可能になります。
6. 参考サイト
配列を使用して GoogleMap に複数のマーカと情報ウィンドウを表示する(その2)の
サンプル C「マップ外のリストからウインドウを開く場合」
Google Map をレスポンシブ対応に
公式ドキュメント:Google Maps JavaScript API
Discussion