最终效果
先来看一下实现后的效果:
功能特点
- ✨ 现代化设计:精致的卡片式 UI,流畅的动画效果
- 🗺️ 交互式地图:基于高德地图 API,支持缩放、拖拽
- 📍 标记点展示:点击标记查看地点详细信息
- 🖼️ 图片灯箱:集成 Fancybox 5.0,优雅的图片预览
- 🌓 暗色模式:完美支持亮色/暗色模式切换
- 📱 响应式:适配桌面端和移动端
- 📊 统计卡片:一体化设计,展示旅行数据
技术选型
地图服务选择
在实现旅行足迹功能时,我对比了几个地图服务:
| 服务商 |
优点 |
缺点 |
选择 |
| 高德地图 |
国内访问稳定、数据详细、API 文档完善 |
需要注册 Key |
✅ 选择 |
| Leaflet + OpenStreetMap |
开源免费、无需 API Key |
国内访问较慢、数据简单 |
❌ |
| Google Maps |
功能强大、全球覆盖 |
需要付费 API、国内需特殊网络 |
❌ |
最终选择高德地图,原因如下:
- 访问速度快:国内服务器,加载速度快
- 数据准确:国内地图数据最为详细
- 免费额度充足:个人开发者每日 30 万次调用
- API 文档完善:提供详细的开发文档和示例
UI 设计风格
在设计方面,我选择了“精致的旅行日记”风格:
- 字体系统:使用 Noto Serif SC(思源宋体)作为标题字体,Inter 作为正文字体
- 视觉层次:细腻的阴影、渐变装饰、流畅动画
- 卡片设计:现代化的卡片布局,统一的视觉语言
- 交互反馈:悬停动画、微妙的过渡效果
实现步骤
1. 申请高德地图 API Key
首先需要注册高德开放平台账号并申请 API Key:
- 访问高德开放平台注册账号
- 完成实名认证
- 进入控制台,创建应用
- 添加 Key,选择「Web端(JS API)」
- 记录下 Key 和 安全密钥(如果有的话)
💡 提示:如果安全密钥显示为 -,说明不需要配置安全密钥,这是正常的。
2. 准备数据文件
创建 source/travel-locations.json 文件,用于存储旅行地点数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| { "version": "1.0.0", "updated": "2025-01-31T12:00:00+08:00", "map_config": { "center": { "lng": 116.397428, "lat": 39.90923 }, "zoom": 5, "map_style": "normal" }, "locations": [ { "id": 1, "name": "故宫博物院", "lng": 116.397428, "lat": 39.90923, "date": "2023-05-01", "description": "中国明清两代的皇家宫殿,世界文化遗产。", "images": ["图片URL"], "category": "景点", "tags": ["历史", "文化", "北京"] } ] }
|
数据字段说明:
| 字段 |
类型 |
必填 |
说明 |
id |
Number |
是 |
唯一标识符 |
name |
String |
是 |
地点名称 |
lng |
Number |
是 |
经度(高德地图 GCJ-02 坐标) |
lat |
Number |
是 |
纬度 |
date |
String |
是 |
到达日期 |
description |
String |
否 |
地点描述 |
images |
Array |
否 |
图片 URL 数组 |
category |
String |
否 |
分类 |
tags |
Array |
否 |
标签数组 |
获取坐标的方法:使用高德地图坐标拾取器获取准确的经纬度。
3. 创建样式文件
创建 source/custom/css/travel-map.css,实现精美的 UI 设计。
核心样式包括:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| :root { --travel-font-heading: 'Noto Serif SC', 'Source Han Serif SC', serif; --travel-font-body: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; --travel-accent: var(--theme, #4caf50); --travel-card-bg: var(--card, #ffffff); --travel-radius: 16px; --travel-transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); }
#travel-map-container { position: relative; width: 100%; height: 650px; background: var(--travel-card-bg); border-radius: var(--travel-radius); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 4px 12px rgba(0, 0, 0, 0.08); transition: var(--travel-transition); }
.journey-stats-card { max-width: 900px; margin: 3rem auto; background: var(--travel-card-bg); border-radius: var(--travel-radius); overflow: hidden; }
|
设计亮点:
- 使用 CSS 变量:完美集成主题的
--theme、--card 等变量
- 现代化阴影:多层阴影叠加,营造立体感
- 流畅动画:使用
cubic-bezier 缓动函数
- 渐变装饰:微妙的渐变背景增加层次感
4. 创建交互逻辑
创建 source/custom/js/travel-map.js,实现地图的交互逻辑。
核心功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| async function loadTravelData() { const response = await fetch('/travel-locations.json'); const data = await response.json(); initMap(data); }
function initMap(data) { const map = new AMap.Map('travel-map', { zoom: data.map_config.zoom, center: [data.map_config.center.lng, data.map_config.center.lat] });
addMarkers(map, data.locations); }
function addMarkers(map, locations) { locations.forEach(location => { const marker = new AMap.Marker({ position: [location.lng, location.lat], title: location.name });
const infoWindow = createInfoWindow(location); marker.on('click', () => { infoWindow.open(map, marker.getPosition()); setTimeout(() => { Fancybox.bind('[data-fancybox="travel-map"]'); }, 300); });
map.add(marker); }); }
|
5. 更新配置文件
在 _config.yml 中添加 CSS 和 JS 引用:
1 2 3 4 5
| inject: head: - <link rel="stylesheet" href="/custom/css/travel-map.css"> script: - <script type="text/javascript" src="/custom/js/travel-map.js"></script>
|
6. 更新探索页面
编辑 source/explore/index.md,添加地图容器和 API Key 配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| --- title: 探索 menu_id: explore
inject: head: - <script src="https://webapi.amap.com/maps?v=2.0&key=您的Key"></script> - <link rel="stylesheet" href="https://gcore.jsdelivr.net/npm/@fancyapps/ui@5.0/dist/fancybox/fancybox.css"> - <script src="https://gcore.jsdelivr.net/npm/@fancyapps/ui@5.0/dist/fancybox/fancybox.umd.js"></script> ---
<div id="travel-map-container"> <div class="travel-map-loading"> <div class="spinner"></div> <p>地图加载中...</p> </div> </div>
<div class="journey-stats-card"> <!-- 统计内容 --> </div>
|
遇到的问题和解决方案
问题 1:数据文件 404 错误
现象:页面报错 HTTP error! status: 404
原因:数据文件放在 source/_data/ 目录下,该目录用于存放数据源文件,不会被 Hexo 复制到生成的站点中。
解决方案:将数据文件移动到 source/ 根目录。
1
| mv source/_data/travel-locations.json source/travel-locations.json
|
问题 2:图片点击下载而不是打开灯箱
现象:点击信息窗口中的图片直接下载,没有 Fancybox 灯箱效果。
原因:地图信息窗口是动态生成的,Fancybox 在页面加载时已经初始化完成,没有自动绑定到后来添加的元素上。
解决方案:在信息窗口打开后,手动重新绑定 Fancybox。
1 2 3 4 5 6 7 8 9 10
| marker.on('click', function() { infoWindow.open(map, marker.getPosition());
setTimeout(() => { if (typeof Fancybox !== 'undefined') { Fancybox.unbind('[data-fancybox="travel-map"]'); Fancybox.bind('[data-fancybox="travel-map"]'); } }, 300); });
|
问题 3:Fancybox 未加载
现象:控制台显示 Fancybox 未加载
原因:探索页面没有 [data-fancybox] 元素,主题的 Fancybox 加载逻辑跳过了该页面。
解决方案:在探索页面强制加载 Fancybox CSS 和 JS。
1 2 3 4
| inject: head: - <link rel="stylesheet" href="https://gcore.jsdelivr.net/npm/@fancyapps/ui@5.0/dist/fancybox/fancybox.css"> - <script src="https://gcore.jsdelivr.net/npm/@fancyapps/ui@5.0/dist/fancybox/fancybox.umd.js"></script>
|
问题 4:高德地图控件报错
现象:控制台报错 AMap.ToolBar is not a constructor
原因:高德地图 API 2.0 中,控件需要通过插件方式加载。
解决方案:暂时移除控件,保持地图简洁。
1 2 3
| function addMapControls(map) { }
|
问题 5:YAML 格式错误
现象:部署时报错 bad indentation of a sequence entry
原因:YAML 中多行脚本需要使用正确的语法。
解决方案:使用 | 符号表示多行字符串。
1 2 3 4 5 6 7 8
| inject: head: - | <script type="text/javascript"> window._AMapSecurityConfig = { securityJsCode: 'xxx' }; </script>
|
设计细节
字体选择
为了营造”旅行日记”的氛围,我选择了:
- 标题字体:Noto Serif SC(思源宋体)- 优雅的衬线字体
- 正文字体:Inter - 现代无衬线字体
1 2 3 4 5 6
| @import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;600;700&family=Inter:wght@400;500;600&display=swap');
:root { --travel-font-heading: 'Noto Serif SC', serif; --travel-font-body: 'Inter', sans-serif; }
|
动画效果
为统计卡片添加了浮动动画:
1 2 3 4 5 6 7 8
| @keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-10px); } }
.journey-stats-icon { animation: float 3s ease-in-out infinite; }
|
暗色模式适配
使用主题变量自动适配:
1 2 3 4 5 6
| .dark #travel-map-container, [data-theme="dark"] #travel-map-container { box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2), 0 4px 12px rgba(0, 0, 0, 0.3); }
|
完整的文件结构
1 2 3 4 5 6 7 8 9 10 11
| hexo-stellar/ ├── source/ │ ├── custom/ │ │ ├── css/ │ │ │ └── travel-map.css # 地图样式 │ │ └── js/ │ │ └── travel-map.js # 地图交互逻辑 │ ├── explore/ │ │ └── index.md # 探索页面 │ └── travel-locations.json # 旅行地点数据 └── _config.yml # 全局配置(添加 inject)
|
后续优化方向
目前实现的功能已经比较完善,但还有一些可以优化的方向:
- 地点分类筛选:添加标签筛选功能,按类别显示地点
- 时间轴模式:按时间顺序展示旅行路线,绘制轨迹
- 数据可视化:添加统计图表,展示每年的旅行次数
- 自定义标记图标:使用自定义图标美化标记点
- 图片懒加载:优化图片加载性能
- 导出功能:支持导出 GPX 文件
总结
通过这次实践,我成功为博客添加了一个精美的旅行足迹功能。整个过程中,我学到了很多:
- 高德地图 API 的使用方法
- Fancybox 5.0 的集成和动态绑定
- Hexo 主题的定制和扩展
- 响应式设计的最佳实践
- 暗色模式的适配技巧
这个功能不仅让博客更加个性化和有趣,也记录了我旅行中的美好回忆。如果你也想为你的博客添加类似功能,希望本文能对你有所帮助!
参考资源
最后:如果你在实现过程中遇到问题,欢迎在评论区交流讨论!