前言
给博客加个天气预报,让访客一眼看到所在城市的实时天气,是个有趣又有用的小功能。本文记录从选型到落地的完整过程——最初尝试了和风天气,最终切换到完全免费的 Open-Meteo API,并把天气卡片放在了个人信息和公告之间的侧边栏位置。
效果概览
| 功能 | 说明 |
|---|---|
| 🌍 IP 自动定位 | 通过 ipapi.co 获取访客城市和坐标,无需手动选择 |
| ☀️ 实时天气 | Open-Meteo 免费 API,显示温度、天气图标和描述 |
| 📍 城市显示 | 自动识别城市名,展示在天气卡片中 |
| 🌬️ 风向风力 | 角度 → 中文风向(北/东北/东…),风速 → 蒲福风级 |
| 💧 湿度气压 | 实时相对湿度 (%) 和海平面气压 (hPa) |
| 👁 能见度 | 实时能见度 (km) |
| 💾 智能缓存 | localStorage 30 分钟缓存,减少 API 请求 |
| 🎨 暗色模式 | 完整适配主题切换,CSS 变量驱动 |
技术选型
初选:和风天气
最初注册了和风天气开发者账号,尝试了两种方式:
- 官方 Widget(
he-plugin-simple):需要加载外部 JS,在导航栏宽度受限的情况下渲染不稳定,且 Widget Key 验证机制导致持续加载失败 - API 直调(
/v7/weather/now):需要 JWT 或 API KEY 认证,浏览器端暴露密钥有安全风险
折腾了两轮之后,决定换一个免费、无需注册、零密钥的方案。
终选:Open-Meteo
Open-Meteo 是一个开源的免费天气 API:
- ✅ 完全免费,无需 API Key
- ✅ 全球覆盖,数据来源包括各国官方气象机构
- ✅ 支持实时天气 + 预报
- ✅ 支持 WMO 天气代码标准
- ✅ 响应速度快,JSON 格式简洁
唯一的缺点是城市名需要额外获取(通过 ipapi.co 的 IP 定位解决)。
核心实现
数据流程
1访客浏览器
2 │
3 ├─ 1. ipapi.co/json → 获取城市名 + 经纬度
4 │
5 ├─ 2. localStorage → 检查缓存(30分钟有效期)
6 │ ├─ 命中 → 直接渲染
7 │ └─ 未命中 ↓
8 │
9 └─ 3. api.open-meteo.com/v1/forecast
10 → 传入经纬度 + 请求参数
11 → 返回实时天气 JSON
12 → 存入 localStorage + 渲染
天气代码映射
Open-Meteo 使用 WMO 天气代码,总共映射了 19 种常见天气到 emoji 图标和中文描述:
1var weatherIcons = {
2 0: '☀️', // 晴
3 1: '🌤️', 2: '⛅', 3: '☁️', // 少云 / 多云 / 阴
4 45: '🌫️', 48: '🌫️', // 雾 / 雾凇
5 51: '🌦️', 53: '🌦️', 55: '🌧️', // 毛毛雨
6 61: '🌧️', 63: '🌧️', 65: '🌧️', // 小雨 / 中雨 / 大雨
7 71: '❄️', 73: '❄️', 75: '❄️', // 小雪 / 中雪 / 大雪
8 80: '🌨️', 81: '🌨️', 82: '🌨️', // 阵雨
9 95: '⛈️', 96: '⛈️', 99: '⛈️' // 雷暴
10};
风向风力计算
风向角度(0-360°)映射到 8 个中文方位:
1function windDir(deg) {
2 var dirs = ['北', '东北', '东', '东南', '南', '西南', '西', '西北'];
3 return dirs[Math.round(deg / 45) % 8] + '风';
4}
风速(km/h)按蒲福风级标准换算成 0-12 级:
| 风速 (km/h) | 风级 | 描述 |
|---|---|---|
| < 1 | 0 级 | 无风 |
| 1-5 | 1 级 | 软风 |
| 6-11 | 2 级 | 轻风 |
| 12-19 | 3 级 | 微风 |
| 20-28 | 4 级 | 和风 |
| 29-38 | 5 级 | 清风 |
| 39-49 | 6 级 | 强风 |
| 50-61 | 7 级 | 疾风 |
| 62-74 | 8 级 | 大风 |
| 75-88 | 9 级 | 烈风 |
| 89-102 | 10 级 | 狂风 |
| 103-117 | 11 级 | 暴风 |
| ≥ 118 | 12 级 | 飓风 |
侧边栏集成
天气卡片插入在个人信息(Profile)和公告(Announcement)之间,通过 Hugo 的 widget 排序机制控制:
1# hugo.toml
2[params.widgets]
3 order = ["profile", "weather", "announcement", ...]
模板结构:
1<div class="widget widget-weather">
2 <h4 class="widget-title">天气预报</h4>
3 <div class="weather-widget-body">
4 <!-- JS 动态渲染:图标 + 温度 + 描述 + 城市 -->
5 </div>
6 <div class="weather-widget-detail">
7 <!-- JS 动态渲染:风向/湿度/气压/能见度 -->
8 </div>
9</div>
缓存策略
1var cacheTimeout = 30 * 60 * 1000; // 30 分钟
2
3// 优先读缓存
4var cached = localStorage.getItem('lumin_weather_omet_v1');
5if (cached) {
6 var data = JSON.parse(cached);
7 if (Date.now() - data.ts < cacheTimeout) {
8 render(data);
9 return; // 命中缓存,不发请求
10 }
11}
这样同一访客在 30 分钟内浏览多篇文章,只请求一次天气 API。
踩坑记录
和风天气 Widget 的坑
和风的 he-plugin-simple widget 在本地开发环境(localhost)下可能因为跨域或 Key 验证机制无法正常渲染,表现为无限「加载中」。即使换到生产环境,Widget Key 与 API Key 的权限体系也容易混淆。
导航栏空间不足
最初把天气放在导航栏搜索框旁边,但导航栏高度只有约 34px,和风 widget 在这个高度下文字渲染异常(只显示一个小白框)。侧边栏空间充裕,更适合展示完整的天气信息。
能见度单位转换
Open-Meteo 返回的能见度单位是米(如 visibility: 10000),需要除以 1000 转换为公里显示。
配置说明
在 hugo.toml 中只需两个参数:
1[params.weather]
2 enable = true # 总开关
3 cacheMinutes = 30 # 缓存时间(分钟)
无需填写任何 API Key,开箱即用。
总结
从和风天气折腾到 Open-Meteo,最终实现了一个零配置、零密钥、全免费的侧边栏天气预报。整个方案只有两个外部依赖:
ipapi.co— IP 定位(免费额度足够个人博客使用)api.open-meteo.com— 实时天气数据(开源免费)
如果你也用 Hugo 搭建博客,可以参考这套方案快速集成天气功能。
留言评论
期待你的想法评论加载中