纯前端显示天气


目录
  1. 1. 天气来源
  2. 2. 代码
  3. 3. 优化

纯前端实现在网站上挂一个漂亮的天气标签。

天气来源

使用openweather提供的免费api,需注册帐号,生成api-key。
数据获取方式:
https://api.openweathermap.org/data/2.5/weather?q=beijing&appid=xxxxxxxx&units=metric&lang=zh_cn

https://api.openweathermap.org/data/2.5/weather?lat=39.91&lon=116.40&appid=xxxxxxxx&units=metric&lang=zh_cn
返回:
{"coord":{"lon":116.3972,"lat":39.9075},"weather":[{"id":800,"main":"Clear","description":"晴","icon":"01n"}],"base":"stations","main":{"temp":12.94,"feels_like":10.66,"temp_min":12.94,"temp_max":12.94,"pressure":1015,"humidity":14,"sea_level":1015,"grnd_level":1010},"visibility":10000,"wind":{"speed":2.96,"deg":297,"gust":8.78},"clouds":{"all":0},"dt":1745764328,"sys":{"type":1,"id":9609,"country":"CN","sunrise":1745702400,"sunset":1745751833},"timezone":28800,"id":1816670,"name":"Beijing","cod":200}

代码

徽章显示的地方:
<img id="weather-badge" src="">

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
38
39
40
41
42
43
<script>
// 配置
const LAT = 39.93; //经纬度
const LON = 119.59;
const API_KEY = "xxxxxxxx"; // 替换为你的API密钥
const CITY_NAME = "今日天气"; // 自定义显示名称

async function updateWeatherBadge() {
try {
// 1. 获取天气数据
const apiUrl = `https://api.openweathermap.org/data/2.5/weather?lat=${LAT}&lon=${LON}&appid=${API_KEY}&units=metric&lang=zh_cn`;
const response = await fetch(apiUrl);
const data = await response.json();

const temp = Math.round(data.main.temp);
const description = data.weather[0].description;

// 2. 根据温度选择颜色
let color;
if (temp < 0) color = "lightblue";
else if (temp < 10) color = "blue";
else if (temp < 20) color = "green";
else if (temp < 30) color = "orange";
else color = "red";

// 3. 生成 Shields.io 徽章 URL
const badgeUrl = `https://img.shields.io/badge/${encodeURIComponent(CITY_NAME)}-${temp}°C_${encodeURIComponent(description)}-${color}.svg`;

// 4. 更新徽章
document.getElementById("weather-badge").src = badgeUrl;

} catch (error) {
console.error("获取天气失败:", error);
// 显示错误徽章
document.getElementById("weather-badge").src =
"https://img.shields.io/badge/天气-加载失败-red.svg";
}
}

// 初始加载 + 每30分钟更新一次
updateWeatherBadge();
setInterval(updateWeatherBadge, 30 * 60 * 1000);
</script>

优化

30分钟缓存:

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
<!-- 徽章容器(支持点击刷新) -->
<a href="javascript:void(0)" onclick="forceRefreshWeather()" title="点击刷新">
<img id="weather-badge"
src="https://img.shields.io/badge/天气-加载中-lightgrey.svg"
alt="实时天气">
</a>

<script>
// ===== 配置 =====
const CONFIG = {
LAT: 39.93,
LON: 119.59,
API_KEY: "xxxxxxxxxxx",
CACHE_KEY: "weather_cache_v2",
CACHE_TTL: 30 * 60 * 1000, // 30分钟缓存
RETRY_DELAY: 5000 // 失败后5秒重试
};

// ===== 核心函数 =====
async function updateWeather() {
try {
// 1. 检查缓存是否有效
const cachedData = getValidCache();
if (cachedData) {
renderBadge(cachedData);
return;
}

// 2. 调用API获取新数据
const apiUrl = `https://api.openweathermap.org/data/2.5/weather?lat=${CONFIG.LAT}&lon=${CONFIG.LON}&appid=${CONFIG.API_KEY}&units=metric&lang=zh_cn`;
const response = await fetchWithTimeout(apiUrl, { timeout: 3000 });

if (!response.ok) throw new Error(`HTTP ${response.status}`);

const data = await response.json();
const processedData = processData(data);

// 3. 更新缓存和界面
saveCache(processedData);
renderBadge(processedData);

} catch (error) {
console.error("[天气] 更新失败:", error);
handleError(error);
}
}

// ===== 工具函数 =====
function processData(apiData) {
const temp = Math.round(apiData.main.temp);
return {
temp,
desc: apiData.weather[0].description,
color: getTempColor(temp),
timestamp: Date.now()
};
}

function getTempColor(temp) {
if (temp < 0) return "lightblue";
if (temp < 10) return "blue";
if (temp < 20) return "green";
if (temp < 30) return "orange";
return "red";
}

function renderBadge(data) {
const badgeUrl = `https://img.shields.io/badge/天气-${data.temp}°C_${encodeURIComponent(data.desc)}-${data.color}.svg?cacheBuster=${Date.now()}`;
document.getElementById("weather-badge").src = badgeUrl;
}

// ===== 缓存管理 =====
function getValidCache() {
const raw = localStorage.getItem(CONFIG.CACHE_KEY);
if (!raw) return null;

try {
const data = JSON.parse(raw);
const isExpired = (Date.now() - data.timestamp) > CONFIG.CACHE_TTL;
return isExpired ? null : data;
} catch {
return null;
}
}

function saveCache(data) {
localStorage.setItem(CONFIG.CACHE_KEY, JSON.stringify(data));
}

// ===== 错误处理 =====
function handleError(error) {
// 尝试显示缓存(即使过期)
const cached = getValidCache() || JSON.parse(localStorage.getItem(CONFIG.CACHE_KEY));
if (cached) {
renderBadge(cached);
console.warn("[天气] 使用缓存数据");
} else {
document.getElementById("weather-badge").src =
"https://img.shields.io/badge/天气-服务异常-red.svg";
}

// 自动重试
setTimeout(updateWeather, CONFIG.RETRY_DELAY);
}

async function fetchWithTimeout(resource, { timeout = 5000 } = {}) {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);

const response = await fetch(resource, {
signal: controller.signal
});
clearTimeout(id);

return response;
}

// ===== 用户交互 =====
function forceRefreshWeather() {
localStorage.removeItem(CONFIG.CACHE_KEY);
document.getElementById("weather-badge").src =
"https://img.shields.io/badge/天气-刷新中-yellow.svg";
updateWeather();
}

// ===== 初始化 =====
document.addEventListener('DOMContentLoaded', () => {
updateWeather();
// 定时检查(实际更新取决于缓存TTL)
setInterval(updateWeather, CONFIG.CACHE_TTL / 2);
});
</script>