在 Hugo 部落格中加入互動式旅行地圖 (Leaflet + JSON)
最近想在部落格的 Travel 頁面頂部加上一個可以標記去過哪些地方的互動式地圖,幾經思考後決定使用開源且輕量的 Leaflet.js,搭配自訂的 JSON 檔案來管理景點和顏色。
🗺️ 實作目標
- 在
/life/travel的頂部固定一個地圖,顯示去過的國家與地點。 - 透過 JSON 檔案集中管理景點資料 (包含中英文名稱、經緯度、以及專屬顏色)。
- 使用 Leaflet.js 渲染地圖,並根據 JSON 自動計算邊界縮放 (
fitBounds) 以容納所有標記。 - 將地圖封裝成 Hugo Shortcode
方便在不同語系頁面重複使用。
💾 建立資料檔 (Data Layer)
首先,在 Hugo 專案的 data/ 目錄下建立一個 travel.json,之後如果有新增旅程,只要修改這裡就可以,不用動到程式碼:
[
{
"name_zh": "台北 (台灣)",
"name_en": "Taipei (Taiwan)",
"lat": 25.033,
"lng": 121.5654,
"color": "red"
},
{
"name_zh": "福岡市",
"name_en": "Fukuoka City",
"lat": 33.5902,
"lng": 130.4017,
"color": "red"
}
]
🧩 封裝 Hugo Shortcode (Presentation Layer)
接著,在 layouts/shortcodes/ 底下新增 travel-map.html。我們透過 CDN 引入 Leaflet 的 CSS 和 JS,並讀取剛剛建立的 JSON 資料。
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" crossorigin="" />
<script src="https://unpkg.com/[email protected]/dist/leaflet.js" crossorigin=""></script>
<div id="travel-map" style="height: 400px; width: 100%; border-radius: 8px; margin-bottom: 2rem; z-index: 1;"></div>
<script>
document.addEventListener("DOMContentLoaded", function () {
// 修正 CDN 預設 icon 路徑問題
delete L.Icon.Default.prototype._getIconUrl;
L.Icon.Default.mergeOptions({
iconRetinaUrl: 'https://unpkg.com/[email protected]/dist/images/marker-icon-2x.png',
iconUrl: 'https://unpkg.com/[email protected]/dist/images/marker-icon.png',
shadowUrl: 'https://unpkg.com/[email protected]/dist/images/marker-shadow.png',
});
// 讀取 Hugo data/travel.json 的資料
var travelData = \{\{ site.Data.travel | jsonify | safeJS \}\};
var lang = "\{\{ .Page.Lang \}\}";
var map = L.map('travel-map', {
scrollWheelZoom: true // 允許透過滾輪縮放地圖
});
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
var bounds = [];
if (travelData && travelData.length > 0) {
travelData.forEach(function (loc) {
var name = lang === "en" ? loc.name_en : loc.name_zh;
var markerOptions = {};
// 根據 color 屬性套用不同的 Marker 顏色
if (loc.color) {
markerOptions.icon = new L.Icon({
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-' + loc.color + '.png',
shadowUrl: 'https://unpkg.com/[email protected]/dist/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
}
var marker = L.marker([loc.lat, loc.lng], markerOptions).addTo(map)
.bindPopup("<b>" + name + "</b>");
bounds.push([loc.lat, loc.lng]);
});
// 自動計算邊界並設定 50px 的 padding
map.fitBounds(bounds, { padding: [50, 50] });
} else {
map.setView([23.6978, 120.9605], 5); // 預設視角
}
});
</script>
💡 Note: 這裡特別使用了 leaflet-color-markers 來替換預設的藍色標記,這樣就能替不同趟的旅程貼上專屬顏色的圖示!
📄 寫入頁面
完成 Shortcode 之後,只需要在任何 Markdown 檔案裡加上這行指令:
\{\{< travel-map >\}\}
自動生成互動地圖的同時還支援中英雙語系,讓 English 頁面的 popups 自動顯示英文地名!
🚀 What’s Next?
目前需要透過手動修改 travel.json 標記去過的地點,實際上使用手機拍照時,照片資訊都會紀錄位置資訊,未來說不定可以透過照片資訊,直接標註在地圖上就好~