2025/12/25 每日总结 天气预报应用 - OpenWeatherMap
本文以日志形式记录我从零搭建这个天气预报网页的全过程,重点讲解OpenWeatherMap API的配置与实际应用,以及如何结合原生HTML/CSS/JS实现丰富的视觉和交互效果。
一、前期准备
1. 技术栈选型
- 核心技术:HTML5 + CSS3 + JavaScript(原生开发,无框架依赖,降低学习门槛)
- 辅助工具库:
- Chart.js:绘制温度趋势图表,直观展示天气变化规律
- Font Awesome:提供丰富的天气图标,快速提升视觉效果
- 开发工具:VS Code(搭配Live Server插件)、Chrome开发者工具
2. API选择:为什么选OpenWeatherMap?
对比多个天气API后,最终选择OpenWeatherMap的原因:
- 免费额度充足:每分钟60次请求,完全满足个人/测试使用
- 接口丰富:支持当前天气、7天预报、小时预报、空气质量等全维度数据
- 文档清晰:参数说明、返回格式一目了然,支持多语言/多单位配置
- 全球覆盖:支持国内外城市查询,数据更新及时
二、OpenWeatherMap API核心配置
1. 注册与API Key获取
这是调用API的第一步,步骤如下:
- 访问OpenWeatherMap官网,点击「Sign Up」完成账号注册
- 登录后进入API Keys页面
- 系统会默认生成一个API Key,也可点击「Generate」自定义创建
- ⚠️ 关键提醒:新生成的API Key需要等待10-20分钟生效,立即调用会提示"invalid API key"
2. 核心接口说明
本次项目主要用到3个核心接口,整理如下:
| 接口用途 | 接口地址 | 核心参数 |
|---|---|---|
| 当前天气 | https://api.openweathermap.org/data/2.5/weather |
q(城市名)、appid(API Key)、units(单位)、lang(语言) |
| 5天预报 | https://api.openweathermap.org/data/2.5/forecast |
同上 |
| 空气质量 | https://api.openweathermap.org/data/2.5/air_pollution |
lat(纬度)、lon(经度)、appid |
接口请求示例(当前天气)
https://api.openweathermap.org/data/2.5/weather?q=beijing,cn&appid=你的API_KEY&units=metric&lang=zh_cn
units=metric:返回摄氏度(imperial为华氏度)lang=zh_cn:返回中文天气描述q=beijing,cn:指定查询城市(格式:城市名,国家代码)
3. 接口返回格式解析
API返回JSON格式数据,核心字段说明:
{
"coord": {"lon": 116.4, "lat": 39.9}, // 经纬度
"weather": [{"id": 800, "main": "Clear", "description": "晴"}], // 天气核心信息
"main": {
"temp": 25, // 当前温度
"feels_like": 26, // 体感温度
"temp_min": 18, // 最低温
"temp_max": 28, // 最高温
"humidity": 65 // 湿度
},
"wind": {"speed": 3.5}, // 风速
"sys": {
"sunrise": 1759123800, // 日出时间戳
"sunset": 1759170600 // 日落时间戳
},
"name": "北京" // 城市名
}
三、代码实现:从API调用到页面渲染
1. 核心封装:API请求函数
首先封装通用的API请求函数,处理异步请求和错误捕获:
// 替换为你的API Key
const API_KEY = "YOUR_OPENWEATHERMAP_API_KEY";
const BASE_URL = "https://api.openweathermap.org/data/2.5/";
// 获取当前天气数据
async function getCurrentWeather(city) {
try {
const response = await fetch(`${BASE_URL}weather?q=${city}&appid=${API_KEY}&units=metric&lang=zh_cn`);
if (!response.ok) throw new Error(`城市 ${city} 未找到`);
const data = await response.json();
return data;
} catch (error) {
console.error("获取天气失败:", error);
alert(error.message);
return null;
}
}
// 获取5天预报数据(整理为按天分组)
async function getForecast(city) {
try {
const response = await fetch(`${BASE_URL}forecast?q=${city}&appid=${API_KEY}&units=metric&lang=zh_cn`);
if (!response.ok) throw new Error("获取预报数据失败");
const data = await response.json();
// 按日期分组,提取每天核心数据
const dailyForecast = {};
data.list.forEach(item => {
const date = new Date(item.dt * 1000).toLocaleDateString();
if (!dailyForecast[date]) dailyForecast[date] = [];
dailyForecast[date].push(item);
});
// 提取每天最高/最低温、天气描述
return Object.keys(dailyForecast).map(date => {
const dayData = dailyForecast[date];
const temps = dayData.map(item => item.main.temp);
return {
date,
maxTemp: Math.max(...temps),
minTemp: Math.min(...temps),
weather: dayData[0].weather[0],
temp: dayData[Math.floor(dayData.length/2)].main.temp
};
}).slice(0, 6); // 取今天+未来5天
} catch (error) {
console.error("获取预报失败:", error);
alert(error.message);
return null;
}
}
2. 数据渲染:将API数据更新到页面
(1)渲染当前天气
function renderCurrentWeather(data) {
if (!data) return;
// 显示加载状态
document.getElementById('current-loading').style.display = 'block';
document.getElementById('current-weather-content').style.display = 'none';
// 解析数据(时间戳转本地时间)
const sunrise = new Date(data.sys.sunrise * 1000).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
const sunset = new Date(data.sys.sunset * 1000).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
// 更新DOM元素
document.getElementById('city-name').textContent = `${data.name}, ${data.sys.country}`;
document.getElementById('current-temp').textContent = `${Math.round(data.main.temp)}°C`;
document.getElementById('current-description').textContent = data.weather[0].description;
document.getElementById('feels-like').textContent = `${Math.round(data.main.feels_like)}°C`;
document.getElementById('humidity').textContent = `${data.main.humidity}%`;
document.getElementById('wind').textContent = `${data.wind.speed} m/s`;
document.getElementById('sunrise').textContent = sunrise;
document.getElementById('sunset').textContent = sunset;
// 更新背景和图标(根据天气类型)
updateWeatherBackground(data.weather[0].main.toLowerCase());
updateWeatherIcon(data.weather[0].main.toLowerCase());
// 隐藏加载,显示内容
document.getElementById('current-loading').style.display = 'none';
document.getElementById('current-weather-content').style.display = 'block';
}
// 根据天气类型更新页面背景
function updateWeatherBackground(weatherType) {
const body = document.body;
body.classList.remove('clear-day', 'cloudy', 'rainy', 'snowy', 'foggy');
switch(weatherType) {
case 'clear': body.classList.add('clear-day'); break;
case 'clouds': body.classList.add('cloudy'); break;
case 'rain': body.classList.add('rainy'); break;
case 'snow': body.classList.add('snowy'); break;
case 'fog': body.classList.add('foggy'); break;
default: body.classList.add('clear-day');
}
}
(2)渲染温度趋势图表(Chart.js)
function renderTempChart(forecastData) {
if (!forecastData) return;
// 销毁原有图表,避免重复渲染
if (window.tempChart) window.tempChart.destroy();
// 处理图表标签和数据
const labels = forecastData.map((item, idx) => {
return idx === 0 ? '今天' : new Date(item.date).toLocaleDateString('zh-CN', {weekday: 'short'});
});
const maxTemps = forecastData.map(item => item.maxTemp);
const minTemps = forecastData.map(item => item.minTemp);
// 创建图表
const ctx = document.getElementById('temperature-chart').getContext('2d');
window.tempChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: '最高温度 (°C)',
data: maxTemps,
borderColor: '#FF6B6B',
backgroundColor: 'rgba(255, 107, 107, 0.1)',
tension: 0.3,
fill: true
},
{
label: '最低温度 (°C)',
data: minTemps,
borderColor: '#4ECDC4',
backgroundColor: 'rgba(78, 205, 196, 0.1)',
tension: 0.3,
fill: true
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: { grid: { color: 'rgba(255,255,255,0.1)' }, ticks: { color: '#fff' } },
x: { grid: { color: 'rgba(255,255,255,0.1)' }, ticks: { color: '#fff' } }
},
plugins: {
legend: { labels: { color: '#fff' } },
tooltip: { backgroundColor: 'rgba(0,0,0,0.7)', titleColor: '#fff' }
}
}
});
}
3. 交互功能实现
(1)搜索与热门城市绑定
// 搜索按钮事件
document.getElementById('search-btn').addEventListener('click', async () => {
const city = document.getElementById('city-input').value.trim();
if (!city) { alert('请输入城市名称'); return; }
// 获取并渲染数据
const currentData = await getCurrentWeather(city);
if (currentData) {
renderCurrentWeather(currentData);
const forecastData = await getForecast(city);
if (forecastData) {
renderForecast(forecastData); // 渲染未来预报(需自定义实现)
renderTempChart(forecastData);
}
}
});
// 热门城市快速查询
document.querySelectorAll('.popular-city').forEach(btn => {
btn.addEventListener('click', async () => {
const city = btn.dataset.city;
document.getElementById('city-input').value = city;
const currentData = await getCurrentWeather(city);
if (currentData) {
renderCurrentWeather(currentData);
const forecastData = await getForecast(city);
if (forecastData) renderTempChart(forecastData);
}
});
});
(2)温度单位切换(摄氏度/华氏度)
// 单位转换核心逻辑
function convertTempUnit(isCelsius) {
document.querySelectorAll('.temp, .forecast-temp, .feels-like').forEach(el => {
if (el.textContent.includes('°')) {
const num = parseFloat(el.textContent);
let newTemp;
if (isCelsius) {
newTemp = Math.round((num - 32) * 5/9); // 华氏转摄氏
el.textContent = `${newTemp}°C`;
} else {
newTemp = Math.round(num * 9/5 + 32); // 摄氏转华氏
el.textContent = `${newTemp}°F`;
}
}
});
}
// 绑定切换事件
document.getElementById('celsius-btn').addEventListener('click', () => {
celsiusBtn.classList.add('active');
fahrenheitBtn.classList.remove('active');
convertTempUnit(true);
});
document.getElementById('fahrenheit-btn').addEventListener('click', () => {
fahrenheitBtn.classList.add('active');
celsiusBtn.classList.remove('active');
convertTempUnit(false);
});
4. 视觉优化:响应式设计与动画
(1)响应式适配
通过CSS Media Query适配移动端和桌面端:
/* 平板适配 */
@media (max-width: 768px) {
h1 { font-size: 2.2rem; }
.current-weather { padding: 20px; }
.forecast { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
}
/* 手机适配 */
@media (max-width: 480px) {
h1 { font-size: 1.8rem; }
.search-box { flex-direction: column; }
#city-input, #search-btn { width: 100%; }
.forecast { grid-template-columns: 1fr; }
}
(2)天气动画效果
为不同天气类型添加动态视觉效果:
/* 云朵移动动画 */
.cloud {
position: absolute;
width: 120px;
height: 40px;
background: rgba(255,255,255,0.8);
border-radius: 50px;
animation: cloud-move 20s linear infinite;
}
@keyframes cloud-move {
0% { transform: translateX(-150px); }
100% { transform: translateX(calc(100vw + 150px)); }
}
/* 下雨动画 */
.rain {
width: 2px;
height: 15px;
background: rgba(255,255,255,0.7);
animation: rain-fall 1s linear infinite;
}
@keyframes rain-fall {
0% { transform: translateY(-50px); }
100% { transform: translateY(100px); }
}
四、踩坑记录与解决方案
| 问题 | 原因 | 解决方法 |
|---|---|---|
| API Key无效 | 新生成的Key未生效 | 等待10-20分钟,或重新生成Key |
| 跨域报错 | 浏览器同源策略限制 | 使用Live Server插件,或配置CORS代理 |
| 时间显示错误 | API返回UTC时间戳 | 用new Date(timestamp * 1000)转换为本地时间 |
| 城市查询失败 | 城市名格式错误 | 统一使用「城市名,国家代码」格式(如shanghai,cn) |
五、完整代码
以下是整合所有功能的完整HTML代码(需替换API Key):
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>城市天气预报查询</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
/* 此处省略完整CSS样式,可使用原文中的样式代码 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);
background-size: 400% 400%;
color: #fff;
min-height: 100vh;
padding: 20px;
transition: background 1.5s ease;
}
/* 其余样式代码请参考原文 */
</style>
</head>
<body class="clear-day">
<div class="container">
<!-- HTML结构部分请参考原文 -->
</div>
<script>
// 替换为你的API Key
const API_KEY = "YOUR_API_KEY";
const BASE_URL = "https://api.openweathermap.org/data/2.5/";
// 此处粘贴上述所有JavaScript函数
// 包括getCurrentWeather、getForecast、renderCurrentWeather等
</script>
</body>
</html>
总结
核心知识点回顾
- API配置核心:获取有效API Key是基础,理解接口参数(单位、语言)和返回格式是关键
- 数据处理重点:需将API返回的原始数据(如时间戳、数组)转换为页面展示所需格式
- 交互体验优化:响应式设计、天气动画、单位切换等细节能显著提升用户体验
- 错误处理不可少:捕获API调用失败、城市不存在等异常,增强程序鲁棒性
扩展方向
这个基础版本还有很多可优化的地方:
- 增加地理位置自动定位(Geolocation API)
- 完善空气质量显示(调用air_pollution接口)
- 加入本地存储(localStorage)保存历史查询记录
- 增加天气预警、生活建议等个性化功能

浙公网安备 33010602011771号