实战项目5:天气应用
项目介绍
在这个项目中,我们将创建一个功能完整的天气应用,该应用允许用户获取实时天气数据、查看天气预报、搜索不同城市的天气信息,并支持地理位置定位。这个项目将帮助我们学习JavaScript的API集成、地理位置定位、异步编程、DOM操作和响应式设计等核心概念。
项目需求
- 显示当前城市的实时天气信息
- 支持地理位置定位,自动获取用户所在城市的天气
- 支持通过搜索框搜索不同城市的天气
- 显示当前天气的详细信息:温度、湿度、风速、气压等
- 显示未来几天的天气预报
- 支持温度单位切换(摄氏度/华氏度)
- 根据天气状况显示相应的图标和背景
- 显示日出日落时间
- 支持刷新天气数据
- 处理各种边界情况(如网络错误、城市不存在等)
- 页面布局简洁美观,响应式设计
技术栈
- HTML5
- CSS3
- JavaScript (ES6+)
- 天气API(OpenWeatherMap API)
项目结构
weather-app-project/
├── index.html # 主页面
├── styles.css # 样式文件
└── script.js # JavaScript文件实现步骤
1. 创建HTML结构
首先,我们需要创建HTML结构,包括天气应用的各个组件:标题、搜索框、当前天气信息、详细天气数据和天气预报。
<!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="styles.css">
<!-- 引入天气图标库 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/weather-icons/2.0.12/css/weather-icons.min.css">
</head>
<body>
<div class="weather-app">
<!-- 头部区域 -->
<header class="app-header">
<h1 class="app-title">天气应用</h1>
<div class="search-container">
<input
type="text"
id="search-input"
placeholder="搜索城市..."
autocomplete="off"
>
<button id="search-btn" class="search-btn">
<i class="wi wi-search"></i>
</button>
</div>
<button id="location-btn" class="location-btn">
<i class="wi wi-location"></i>
</button>
</header>
<!-- 主要内容区域 -->
<main class="app-main">
<!-- 加载状态 -->
<div class="loading" id="loading">
<div class="loading-spinner"></div>
<p>加载天气数据中...</p>
</div>
<!-- 错误状态 -->
<div class="error" id="error" style="display: none;">
<i class="wi wi-alerts-danger"></i>
<p id="error-message">无法获取天气数据</p>
<button id="retry-btn" class="retry-btn">重试</button>
</div>
<!-- 天气信息区域 -->
<div class="weather-info" id="weather-info" style="display: none;">
<!-- 当前天气 -->
<section class="current-weather">
<div class="location">
<h2 id="city-name">北京</h2>
<p id="date-time">2023年10月1日 星期日 14:30</p>
</div>
<div class="weather-main">
<div class="temperature">
<span id="current-temp">22</span>
<span class="temp-unit">
<span id="celsius" class="active">°C</span>
<span class="divider">/</span>
<span id="fahrenheit">°F</span>
</span>
</div>
<div class="weather-details">
<div class="weather-icon">
<i id="weather-icon" class="wi wi-day-sunny"></i>
</div>
<div class="weather-desc">
<p id="weather-description">晴天</p>
</div>
</div>
</div>
<div class="weather-stats">
<div class="stat-item">
<i class="wi wi-humidity"></i>
<span id="humidity">50%</span>
<span class="stat-label">湿度</span>
</div>
<div class="stat-item">
<i class="wi wi-strong-wind"></i>
<span id="wind-speed">10 km/h</span>
<span class="stat-label">风速</span>
</div>
<div class="stat-item">
<i class="wi wi-barometer"></i>
<span id="pressure">1013 hPa</span>
<span class="stat-label">气压</span>
</div>
<div class="stat-item">
<i class="wi wi-cloud"></i>
<span id="cloudiness">0%</span>
<span class="stat-label">云量</span>
</div>
</div>
</section>
<!-- 日出日落信息 -->
<section class="sun-info">
<div class="sun-item">
<i class="wi wi-sunrise"></i>
<div class="sun-time">
<span id="sunrise">06:30</span>
<span class="sun-label">日出</span>
</div>
</div>
<div class="sun-divider"></div>
<div class="sun-item">
<i class="wi wi-sunset"></i>
<div class="sun-time">
<span id="sunset">18:30</span>
<span class="sun-label">日落</span>
</div>
</div>
</section>
<!-- 天气预报 -->
<section class="forecast">
<h3 class="forecast-title">未来天气预报</h3>
<div class="forecast-container" id="forecast-container"></div>
</section>
</div>
</main>
<!-- 页脚区域 -->
<footer class="app-footer">
<p>数据来源: <a href="https://openweathermap.org/" target="_blank">OpenWeatherMap API</a></p>
</footer>
</div>
<script src="script.js"></script>
</body>
</html>2. 添加CSS样式
接下来,我们需要添加CSS样式,使天气应用看起来更加美观和易用。
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Arial', sans-serif;
background-color: #f0f0f0;
color: #333;
line-height: 1.6;
}
/* 天气应用容器 */
.weather-app {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
color: white;
display: flex;
flex-direction: column;
}
/* 头部区域样式 */
.app-header {
padding: 20px;
background-color: rgba(0, 0, 0, 0.2);
backdrop-filter: blur(10px);
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
}
.app-title {
font-size: 28px;
font-weight: bold;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
/* 搜索容器样式 */
.search-container {
display: flex;
width: 100%;
max-width: 500px;
gap: 10px;
}
#search-input {
flex: 1;
padding: 12px 20px;
border: none;
border-radius: 25px;
font-size: 16px;
background-color: rgba(255, 255, 255, 0.9);
color: #333;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
#search-input:focus {
outline: none;
background-color: white;
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
}
.search-btn, .location-btn {
background-color: rgba(255, 255, 255, 0.9);
color: #333;
border: none;
border-radius: 50%;
width: 50px;
height: 50px;
font-size: 18px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
.search-btn:hover, .location-btn:hover {
background-color: white;
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
}
.search-btn:active, .location-btn:active {
transform: translateY(0);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
/* 主要内容区域样式 */
.app-main {
flex: 1;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
/* 加载状态样式 */
.loading {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
}
.loading-spinner {
width: 60px;
height: 60px;
border: 4px solid rgba(255, 255, 255, 0.3);
border-top: 4px solid white;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading p {
font-size: 18px;
color: rgba(255, 255, 255, 0.9);
}
/* 错误状态样式 */
.error {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
text-align: center;
max-width: 500px;
}
.error i {
font-size: 64px;
color: #ff6b6b;
}
.error p {
font-size: 18px;
color: rgba(255, 255, 255, 0.9);
}
.retry-btn {
background-color: rgba(255, 255, 255, 0.9);
color: #333;
border: none;
border-radius: 25px;
padding: 12px 24px;
font-size: 16px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
.retry-btn:hover {
background-color: white;
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
}
/* 天气信息区域样式 */
.weather-info {
width: 100%;
max-width: 1200px;
}
/* 当前天气样式 */
.current-weather {
background-color: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 30px;
margin-bottom: 20px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.location {
text-align: center;
margin-bottom: 20px;
}
#city-name {
font-size: 32px;
font-weight: bold;
margin-bottom: 5px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
#date-time {
font-size: 16px;
color: rgba(255, 255, 255, 0.8);
}
.weather-main {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
flex-wrap: wrap;
gap: 20px;
}
.temperature {
font-size: 72px;
font-weight: bold;
display: flex;
align-items: baseline;
gap: 10px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
#current-temp {
font-size: 72px;
}
.temp-unit {
font-size: 32px;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
user-select: none;
}
.temp-unit span {
transition: all 0.3s ease;
}
.temp-unit span.active {
color: white;
font-weight: bold;
}
.temp-unit span:hover {
color: white;
}
.weather-details {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.weather-icon {
font-size: 64px;
filter: drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.3));
}
#weather-description {
font-size: 24px;
text-transform: capitalize;
color: rgba(255, 255, 255, 0.9);
}
/* 天气统计信息样式 */
.weather-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 20px;
margin-top: 30px;
}
.stat-item {
background-color: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
transition: all 0.3s ease;
}
.stat-item:hover {
background-color: rgba(255, 255, 255, 0.2);
transform: translateY(-5px);
}
.stat-item i {
font-size: 32px;
color: rgba(255, 255, 255, 0.9);
}
.stat-item span:first-of-type {
font-size: 24px;
font-weight: bold;
}
.stat-label {
font-size: 14px;
color: rgba(255, 255, 255, 0.7);
text-transform: uppercase;
letter-spacing: 1px;
}
/* 日出日落信息样式 */
.sun-info {
background-color: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 20px;
margin-bottom: 20px;
display: flex;
justify-content: space-around;
align-items: center;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.sun-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.sun-item i {
font-size: 48px;
color: rgba(255, 255, 255, 0.9);
filter: drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.3));
}
.sun-time {
display: flex;
flex-direction: column;
align-items: center;
}
.sun-time span:first-of-type {
font-size: 24px;
font-weight: bold;
}
.sun-label {
font-size: 14px;
color: rgba(255, 255, 255, 0.7);
text-transform: uppercase;
letter-spacing: 1px;
}
.sun-divider {
width: 2px;
height: 80px;
background-color: rgba(255, 255, 255, 0.3);
}
/* 天气预报样式 */
.forecast {
background-color: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 30px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.forecast-title {
font-size: 24px;
font-weight: bold;
margin-bottom: 20px;
text-align: center;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
.forecast-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 15px;
}
.forecast-item {
background-color: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
transition: all 0.3s ease;
}
.forecast-item:hover {
background-color: rgba(255, 255, 255, 0.2);
transform: translateY(-5px);
}
.forecast-date {
font-size: 16px;
font-weight: bold;
color: rgba(255, 255, 255, 0.9);
}
.forecast-icon {
font-size: 32px;
filter: drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.3));
}
.forecast-temp {
font-size: 18px;
font-weight: bold;
}
.forecast-desc {
font-size: 14px;
text-transform: capitalize;
color: rgba(255, 255, 255, 0.8);
text-align: center;
}
/* 页脚样式 */
.app-footer {
padding: 20px;
background-color: rgba(0, 0, 0, 0.2);
text-align: center;
font-size: 14px;
color: rgba(255, 255, 255, 0.7);
}
.app-footer a {
color: white;
text-decoration: none;
font-weight: bold;
transition: all 0.3s ease;
}
.app-footer a:hover {
text-decoration: underline;
}
/* 响应式设计 */
@media (max-width: 768px) {
.weather-main {
flex-direction: column;
text-align: center;
}
.temperature {
font-size: 56px;
}
#current-temp {
font-size: 56px;
}
.temp-unit {
font-size: 24px;
}
.weather-icon {
font-size: 48px;
}
#weather-description {
font-size: 20px;
}
.weather-stats {
grid-template-columns: repeat(2, 1fr);
}
.sun-info {
flex-direction: column;
gap: 20px;
}
.sun-divider {
width: 80px;
height: 2px;
}
.forecast-container {
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
}
.forecast-item {
padding: 15px;
}
}
@media (max-width: 480px) {
.app-title {
font-size: 24px;
}
#city-name {
font-size: 24px;
}
.temperature {
font-size: 48px;
}
#current-temp {
font-size: 48px;
}
.temp-unit {
font-size: 20px;
}
.weather-stats {
grid-template-columns: 1fr;
}
.forecast-container {
grid-template-columns: repeat(2, 1fr);
}
}
/* 天气背景样式 */
.weather-app.clear {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.weather-app.clouds {
background: linear-gradient(135deg, #a8c0ff 0%, #3f2b96 100%);
}
.weather-app.rain {
background: linear-gradient(135deg, #a1c4fd 0%, #c2e9fb 100%);
}
.weather-app.thunderstorm {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.weather-app.snow {
background: linear-gradient(135deg, #e0eafc 0%, #cfdef3 100%);
}
.weather-app.mist {
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
}3. 编写JavaScript逻辑
最后,我们需要编写JavaScript逻辑,实现天气应用的所有功能,包括API调用、地理位置定位、数据处理和UI更新等。
// API配置
const API_KEY = 'YOUR_API_KEY'; // 请替换为你的OpenWeatherMap API密钥
const BASE_URL = 'https://api.openweathermap.org/data/2.5';
// DOM元素
const loadingElement = document.getElementById('loading');
const errorElement = document.getElementById('error');
const errorMessageElement = document.getElementById('error-message');
const weatherInfoElement = document.getElementById('weather-info');
const searchInputElement = document.getElementById('search-input');
const searchBtnElement = document.getElementById('search-btn');
const locationBtnElement = document.getElementById('location-btn');
const retryBtnElement = document.getElementById('retry-btn');
const celsiusElement = document.getElementById('celsius');
const fahrenheitElement = document.getElementById('fahrenheit');
// 天气数据
let currentWeatherData = null;
let forecastData = null;
let isCelsius = true;
// 初始化应用
function initApp() {
// 添加事件监听器
searchBtnElement.addEventListener('click', handleSearch);
searchInputElement.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
handleSearch();
}
});
locationBtnElement.addEventListener('click', getCurrentLocation);
retryBtnElement.addEventListener('click', () => {
if (currentWeatherData) {
updateWeatherDisplay();
} else {
getCurrentLocation();
}
});
celsiusElement.addEventListener('click', () => {
isCelsius = true;
updateTemperatureUnit();
updateWeatherDisplay();
});
fahrenheitElement.addEventListener('click', () => {
isCelsius = false;
updateTemperatureUnit();
updateWeatherDisplay();
});
// 默认获取当前位置的天气
getCurrentLocation();
}
// 处理搜索
async function handleSearch() {
const city = searchInputElement.value.trim();
if (city) {
await fetchWeatherByCity(city);
}
}
// 获取当前位置
function getCurrentLocation() {
if (navigator.geolocation) {
showLoading();
navigator.geolocation.getCurrentPosition(
async (position) => {
const { latitude, longitude } = position.coords;
await fetchWeatherByCoords(latitude, longitude);
},
(error) => {
hideLoading();
showError('无法获取您的位置,请手动搜索城市');
console.error('Geolocation error:', error);
}
);
} else {
showError('您的浏览器不支持地理位置定位,请手动搜索城市');
}
}
// 根据城市名获取天气
async function fetchWeatherByCity(city) {
showLoading();
try {
// 获取当前天气
const currentWeatherResponse = await fetch(`${BASE_URL}/weather?q=${city}&appid=${API_KEY}&units=metric`);
if (!currentWeatherResponse.ok) {
throw new Error('城市不存在或无法获取天气数据');
}
currentWeatherData = await currentWeatherResponse.json();
// 获取天气预报
const forecastResponse = await fetch(`${BASE_URL}/forecast?q=${city}&appid=${API_KEY}&units=metric`);
if (!forecastResponse.ok) {
throw new Error('无法获取天气预报数据');
}
forecastData = await forecastResponse.json();
// 更新天气显示
updateWeatherDisplay();
hideLoading();
} catch (error) {
hideLoading();
showError(error.message);
console.error('Error fetching weather data:', error);
}
}
// 根据坐标获取天气
async function fetchWeatherByCoords(latitude, longitude) {
try {
// 获取当前天气
const currentWeatherResponse = await fetch(`${BASE_URL}/weather?lat=${latitude}&lon=${longitude}&appid=${API_KEY}&units=metric`);
if (!currentWeatherResponse.ok) {
throw new Error('无法获取天气数据');
}
currentWeatherData = await currentWeatherResponse.json();
// 获取天气预报
const forecastResponse = await fetch(`${BASE_URL}/forecast?lat=${latitude}&lon=${longitude}&appid=${API_KEY}&units=metric`);
if (!forecastResponse.ok) {
throw new Error('无法获取天气预报数据');
}
forecastData = await forecastResponse.json();
// 更新天气显示
updateWeatherDisplay();
hideLoading();
} catch (error) {
hideLoading();
showError(error.message);
console.error('Error fetching weather data:', error);
}
}
// 更新温度单位显示
function updateTemperatureUnit() {
if (isCelsius) {
celsiusElement.classList.add('active');
fahrenheitElement.classList.remove('active');
} else {
fahrenheitElement.classList.add('active');
celsiusElement.classList.remove('active');
}
}
// 更新天气显示
function updateWeatherDisplay() {
if (!currentWeatherData || !forecastData) return;
// 更新当前天气信息
updateCurrentWeather();
// 更新天气预报
updateForecast();
// 更新背景
updateBackground();
// 显示天气信息,隐藏错误
weatherInfoElement.style.display = 'block';
errorElement.style.display = 'none';
}
// 更新当前天气信息
function updateCurrentWeather() {
// 获取DOM元素
const cityNameElement = document.getElementById('city-name');
const dateTimeElement = document.getElementById('date-time');
const currentTempElement = document.getElementById('current-temp');
const weatherIconElement = document.getElementById('weather-icon');
const weatherDescriptionElement = document.getElementById('weather-description');
const humidityElement = document.getElementById('humidity');
const windSpeedElement = document.getElementById('wind-speed');
const pressureElement = document.getElementById('pressure');
const cloudinessElement = document.getElementById('cloudiness');
const sunriseElement = document.getElementById('sunrise');
const sunsetElement = document.getElementById('sunset');
// 更新城市名
cityNameElement.textContent = `${currentWeatherData.name}, ${currentWeatherData.sys.country}`;
// 更新日期时间
const now = new Date();
dateTimeElement.textContent = now.toLocaleString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric',
weekday: 'long',
hour: '2-digit',
minute: '2-digit'
});
// 更新温度
let temp = currentWeatherData.main.temp;
if (!isCelsius) {
temp = celsiusToFahrenheit(temp);
}
currentTempElement.textContent = Math.round(temp);
// 更新天气图标
const weatherIcon = getWeatherIcon(currentWeatherData.weather[0].icon);
weatherIconElement.className = `wi ${weatherIcon}`;
// 更新天气描述
weatherDescriptionElement.textContent = currentWeatherData.weather[0].description;
// 更新湿度
humidityElement.textContent = `${currentWeatherData.main.humidity}%`;
// 更新风速
windSpeedElement.textContent = `${currentWeatherData.wind.speed} km/h`;
// 更新气压
pressureElement.textContent = `${currentWeatherData.main.pressure} hPa`;
// 更新云量
cloudinessElement.textContent = `${currentWeatherData.clouds.all}%`;
// 更新日出时间
const sunrise = new Date(currentWeatherData.sys.sunrise * 1000);
sunriseElement.textContent = sunrise.toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit'
});
// 更新日落时间
const sunset = new Date(currentWeatherData.sys.sunset * 1000);
sunsetElement.textContent = sunset.toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit'
});
}
// 更新天气预报
function updateForecast() {
const forecastContainerElement = document.getElementById('forecast-container');
// 清空天气预报容器
forecastContainerElement.innerHTML = '';
// 获取每天的天气预报(取每天12点的数据)
const dailyForecasts = [];
const forecastMap = new Map();
forecastData.list.forEach(forecast => {
const date = new Date(forecast.dt * 1000);
const dateStr = date.toDateString();
const hour = date.getHours();
// 只取每天12点左右的数据
if (hour >= 11 && hour <= 13 && !forecastMap.has(dateStr)) {
forecastMap.set(dateStr, forecast);
dailyForecasts.push(forecast);
}
});
// 只显示未来5天的预报
const next5Days = dailyForecasts.slice(0, 5);
// 更新天气预报
next5Days.forEach(forecast => {
const forecastItem = document.createElement('div');
forecastItem.className = 'forecast-item';
const date = new Date(forecast.dt * 1000);
forecastItem.innerHTML = `
<div class="forecast-date">${date.toLocaleDateString('zh-CN', {
weekday: 'short',
month: 'short',
day: 'numeric'
})}</div>
<i class="wi ${getWeatherIcon(forecast.weather[0].icon)} forecast-icon"></i>
<div class="forecast-temp">${Math.round(isCelsius ? forecast.main.temp : celsiusToFahrenheit(forecast.main.temp))}°</div>
<div class="forecast-desc">${forecast.weather[0].description}</div>
`;
forecastContainerElement.appendChild(forecastItem);
});
}
// 更新背景
function updateBackground() {
const weatherAppElement = document.querySelector('.weather-app');
const weatherMain = currentWeatherData.weather[0].main.toLowerCase();
// 移除所有天气背景类
weatherAppElement.className = 'weather-app';
// 添加对应的天气背景类
if (weatherMain === 'clear') {
weatherAppElement.classList.add('clear');
} else if (weatherMain === 'clouds') {
weatherAppElement.classList.add('clouds');
} else if (weatherMain === 'rain' || weatherMain === 'drizzle') {
weatherAppElement.classList.add('rain');
} else if (weatherMain === 'thunderstorm') {
weatherAppElement.classList.add('thunderstorm');
} else if (weatherMain === 'snow') {
weatherAppElement.classList.add('snow');
} else {
weatherAppElement.classList.add('mist');
}
}
// 获取天气图标
function getWeatherIcon(iconCode) {
// 天气图标映射
const iconMap = {
'01d': 'wi-day-sunny',
'01n': 'wi-night-clear',
'02d': 'wi-day-cloudy',
'02n': 'wi-night-alt-cloudy',
'03d': 'wi-cloud',
'03n': 'wi-cloud',
'04d': 'wi-cloudy',
'04n': 'wi-cloudy',
'09d': 'wi-showers',
'09n': 'wi-showers',
'10d': 'wi-day-rain',
'10n': 'wi-night-alt-rain',
'11d': 'wi-thunderstorm',
'11n': 'wi-thunderstorm',
'13d': 'wi-snow',
'13n': 'wi-snow',
'50d': 'wi-fog',
'50n': 'wi-fog'
};
return iconMap[iconCode] || 'wi-na';
}
// 摄氏度转华氏度
function celsiusToFahrenheit(celsius) {
return (celsius * 9/5) + 32;
}
// 显示加载状态
function showLoading() {
loadingElement.style.display = 'flex';
weatherInfoElement.style.display = 'none';
errorElement.style.display = 'none';
}
// 隐藏加载状态
function hideLoading() {
loadingElement.style.display = 'none';
}
// 显示错误信息
function showError(message) {
errorMessageElement.textContent = message;
errorElement.style.display = 'flex';
loadingElement.style.display = 'none';
weatherInfoElement.style.display = 'none';
}
// 初始化应用
initApp();功能说明
- 实时天气显示:显示当前城市的实时天气信息,包括温度、湿度、风速、气压等。
- 地理位置定位:自动获取用户所在城市的天气信息。
- 城市搜索:支持通过搜索框搜索不同城市的天气。
- 温度单位切换:支持在摄氏度和华氏度之间切换。
- 天气预报:显示未来5天的天气预报。
- 日出日落时间:显示当地的日出日落时间。
- 天气图标和背景:根据天气状况显示相应的图标和背景色。
- 错误处理:处理各种边界情况,如网络错误、城市不存在等。
- 响应式设计:适配不同屏幕尺寸,在手机和电脑上都能正常使用。
扩展功能
我们可以进一步扩展天气应用的功能,例如:
- 支持更多天气数据:空气质量、紫外线指数、能见度等。
- 支持小时预报:显示未来24小时的逐小时预报。
- 支持天气预警:显示当地的天气预警信息。
- 支持多个城市管理:允许用户添加多个城市,方便切换查看。
- 支持深色/浅色主题切换:根据用户偏好或时间自动切换主题。
- 支持天气动画:添加天气动画效果,增强视觉体验。
- 支持语音播报:使用语音播报当前天气信息。
- 支持数据缓存:缓存天气数据,在离线时也能查看。
扩展功能示例:空气质量指数
// 获取空气质量数据
async function fetchAirQuality(lat, lon) {
try {
const response = await fetch(`${BASE_URL}/air_pollution?lat=${lat}&lon=${lon}&appid=${API_KEY}`);
if (!response.ok) {
throw new Error('无法获取空气质量数据');
}
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching air quality data:', error);
return null;
}
}
// 显示空气质量指数
function showAirQuality(airQualityData) {
const aqi = airQualityData.list[0].main.aqi;
const aqiText = {
1: '优',
2: '良',
3: '轻度污染',
4: '中度污染',
5: '重度污染'
};
const airQualityElement = document.createElement('div');
airQualityElement.className = 'stat-item';
airQualityElement.innerHTML = `
<i class="wi wi-smoke"></i>
<span>${aqiText[aqi]}</span>
<span class="stat-label">空气质量</span>
`;
const weatherStatsElement = document.querySelector('.weather-stats');
weatherStatsElement.appendChild(airQualityElement);
}项目运行
- 注册OpenWeatherMap账号,获取API密钥
- 将API密钥替换到script.js文件中的YOUR_API_KEY
- 创建上述三个文件(index.html, styles.css, script.js)
- 在浏览器中打开index.html文件
- 开始使用天气应用
项目总结
通过这个天气应用项目,我们学习了:
- 如何使用JavaScript调用第三方API获取数据
- 如何使用地理位置定位获取用户所在位置
- 如何使用异步编程(async/await)处理网络请求
- 如何使用DOM操作更新页面内容
- 如何处理各种边界情况和错误
- 如何实现响应式设计
- 如何根据数据动态更新页面样式
这个项目涵盖了JavaScript开发中的许多重要概念,为我们后续开发更复杂的应用打下了基础。
练习
- 添加空气质量指数显示
- 添加小时预报功能
- 实现多个城市管理功能
- 添加深色/浅色主题切换
- 添加天气动画效果
- 实现数据缓存功能
- 添加语音播报功能
- 添加天气预警信息显示
扩展阅读
通过这个实战项目,我们已经掌握了JavaScript的API集成和地理位置定位,接下来我们将继续学习JavaScript的常见问题和调试技巧。