获取当前位置天气和天气摄影
如何为博客首页实现基于位置的天气和动态背景图片
引言
为博客首页添加动态功能,比如根据用户所在位置显示当地天气、日出日落时间,并加载一张与天气和时间段匹配的高质量背景图片,可以显著提升用户体验。想象晴天时展示阳光普照的田野,雨天时呈现湿润的城市街景,这样的个性化设计既美观又贴心。本文将详细介绍如何实现这一功能,重点放在核心实现逻辑上,而不局限于特定框架,让你可以轻松将其应用到自己的项目中。
技术栈
实现这一功能需要以下技术:
- JavaScript:用于处理 API 请求和逻辑计算,适用于任何前端环境。
- Open-Meteo API:免费天气 API,提供天气代码、日出和日落时间。
- Pexels API:图片 API,用于获取与天气和时间段匹配的高质量背景图片。
- ipapi.co:免费 IP 定位服务,用于获取用户经纬度。
- LocalStorage:浏览器缓存机制,用于存储背景图片,减少 API 请求。
实现步骤
1. 获取用户位置
要根据用户位置获取天气,首先需要用户的经纬度。ipapi.co
提供了一个简单 API,通过 GET 请求 https://ipapi.co/json/
获取位置信息:
async function getLocation() {
try {
const response = await fetch("https://ipapi.co/json/");
const data = await response.json();
return { latitude: data.latitude, longitude: data.longitude };
} catch (error) {
console.error("获取位置失败:", error);
return { latitude: 39.9042, longitude: 116.4074 }; // 默认位置(如北京)
}
}
说明:如果请求失败,代码会回退到默认位置(这里以北京为例),确保功能可用。
2. 获取天气数据
使用 Open-Meteo API 获取天气信息,包括天气代码、日出和日落时间。以下是核心实现:
async function getWeather(latitude, longitude) {
const params = {
latitude,
longitude,
daily: ["sunrise", "sunset"],
current: "weather_code",
timezone: "auto",
};
const url = "https://api.open-meteo.com/v1/forecast";
const responses = await fetchWeatherApi(url, params);
const response = responses[0];
const current = response.current();
const daily = response.daily();
return {
weatherCode: current.variables(0).value(),
sunrise: new Date(Number(daily.variables(0).valuesInt64(0)) * 1000).toLocaleString(),
sunset: new Date(Number(daily.variables(1).valuesInt64(0)) * 1000).toLocaleString(),
};
}
说明:
- 使用
fetchWeatherApi
(需引入 Open-Meteo SDK)发送请求。 timezone: "auto"
自动适配用户时区。对于咱们中国,可以设置为北京的时区,设置为timezone:"Asia/Shanghai"
- 返回的天气代码(
weatherCode
)用于确定天气类型,日出日落时间用于时间段计算。- 天气代码参考:https://open-meteo.com/en/docs 的最下面
3. 映射天气代码
Open-Meteo 的天气代码是数字(例如,0 表示晴天,61 表示雨)。需要将其映射为描述性文本和英文名称,用于显示和图片搜索:
const weatherMap = {
0: { zh: "晴天", en: "sunny" },
1: { zh: "多云", en: "cloudy" },
61: { zh: "雨", en: "rain" },
95: { zh: "雷暴", en: "thunderstorm" },
// 更多映射...
};
function getWeatherDescription(code) {
return weatherMap[code] || { zh: "未知", en: "unknown" };
}
说明:这种映射便于将天气代码转换为中文描述(用于显示)和英文名称(用于图片搜索)。
4. 获取动态背景图片
使用 Pexels API 根据天气和时间段搜索图片(例如,sunny morning
)。为了优化性能,添加了 LocalStorage 缓存,并检查 API 配额:
async function getBackgroundImage(weather, timeOfDay) {
const query = `${weather}+${timeOfDay}`;
const cacheKey = "bgImg";
const cache = localStorage.getItem(cacheKey);
if (cache) {
const cacheData = JSON.parse(cache);
if (Date.now() < cacheData.expires && cacheData.query === query) {
return cacheData.url;
}
}
const response = await fetch(
`https://api.pexels.com/v1/search?query=${query}&per_page=1`,
{ headers: { Authorization: "您的 API 密钥" } }
);
// 检查配额
const limit = response.headers.get("X-Ratelimit-Limit");
const remaining = response.headers.get("X-Ratelimit-Remaining");
const reset = new Date(Number(response.headers.get("X-Ratelimit-Reset")) * 1000);
console.log(`总配额: ${limit}, 剩余: ${remaining}, 重置时间: ${reset}`);
if (remaining === "0") {
console.warn("Pexels API 配额已用尽,将于", reset, "重置");
return "默认图片 URL"; // 提供备用图片
}
const data = await response.json();
const url = data.photos[0]?.src.original || "默认图片 URL";
localStorage.setItem(cacheKey, JSON.stringify({
expires: Date.now() + 1000 * 60 * 60, // 缓存1小时
url,
query,
}));
return url;
}
配额检查说明:
- X-Ratelimit-Limit:每月总请求限额。
- X-Ratelimit-Remaining:剩余请求数。
- X-Ratelimit-Reset:配额重置的 UNIX 时间戳(秒)。
- 如果
remaining
为 0,代码会记录警告并使用默认图片,确保功能不中断。
5. 计算时间段
根据日出日落时间,将一天分为早晨、中午、傍晚和晚上,用于选择合适的背景图片:
function getTimeOfDay(sunrise, sunset) {
const now = Date.now();
const sunriseTime = new Date(sunrise).getTime();
const sunsetTime = new Date(sunset).getTime();
const morningEnd = sunriseTime + 4 * 3600 * 1000; // 日出后4小时
const eveningStart = sunsetTime - 1 * 3600 * 1000; // 日落前1小时
if (now < sunriseTime) return { zh: "晚上", en: "night" };
if (now < morningEnd) return { zh: "早晨", en: "morning" };
if (now < eveningStart) return { zh: "中午", en: "noon" };
return { zh: "傍晚", en: "evening" };
}
说明:时间段基于日出日落时间动态计算,确保图片与实际时间匹配。
6. 整合功能
以下是将上述逻辑整合的主函数:
async function initDynamicBackground() {
const { latitude, longitude } = await getLocation();
const weatherData = await getWeather(latitude, longitude);
const weatherDesc = getWeatherDescription(weatherData.weatherCode);
const timeOfDay = getTimeOfDay(weatherData.sunrise, weatherData.sunset);
const bgUrl = await getBackgroundImage(weatherDesc.en, timeOfDay.en);
// 应用背景图片(例如,设置到 DOM)
document.body.style.backgroundImage = `url(${bgUrl})`;
// 显示天气信息(例如,更新 DOM)
document.getElementById("weather").textContent = weatherDesc.zh;
document.getElementById("sunrise").textContent = weatherData.sunrise;
document.getElementById("sunset").textContent = weatherData.sunset;
}
说明:
- 函数按顺序获取位置、天气、时间段和背景图片。
- 结果用于更新页面背景和显示天气信息。
- 代码使用原生 JavaScript 操作 DOM,适用于任何环境。
完整代码示例
以下是核心功能的完整实现:
const weatherMap = {
0: { zh: "晴天", en: "sunny" },
1: { zh: "多云", en: "cloudy" },
61: { zh: "雨", en: "rain" },
95: { zh: "雷暴", en: "thunderstorm" },
};
async function getLocation() {
try {
const response = await fetch("https://ipapi.co/json/");
const data = await response.json();
return { latitude: data.latitude, longitude: data.longitude };
} catch (error) {
console.error("获取位置失败:", error);
return { latitude: 39.9042, longitude: 116.4074 };
}
}
async function getWeather(latitude, longitude) {
const params = {
latitude,
longitude,
daily: ["sunrise", "sunset"],
current: "weather_code",
timezone: "auto",
};
const url = "https://api.open-meteo.com/v1/forecast";
const responses = await fetchWeatherApi(url, params);
const response = responses[0];
const current = response.current();
const daily = response.daily();
return {
weatherCode: current.variables(0).value(),
sunrise: new Date(Number(daily.variables(0).valuesInt64(0)) * 1000).toLocaleString(),
sunset: new Date(Number(daily.variables(1).valuesInt64(0)) * 1000).toLocaleString(),
};
}
function getWeatherDescription(code) {
return weatherMap[code] || { zh: "未知", en: "unknown" };
}
function getTimeOfDay(sunrise, sunset) {
const now = Date.now();
const sunriseTime = new Date(sunrise).getTime();
const sunsetTime = new Date(sunset).getTime();
const morningEnd = sunriseTime + 4 * 3600 * 1000;
const eveningStart = sunsetTime - 1 * 3600 * 1000;
if (now < sunriseTime) return { zh: "晚上", en: "night" };
if (now < morningEnd) return { zh: "早晨", en: "morning" };
if (now < eveningStart) return { zh: "中午", en: "noon" };
return { zh: "傍晚", en: "evening" };
}
async function getBackgroundImage(weather, timeOfDay) {
const query = `${weather}+${timeOfDay}`;
const cacheKey = "bgImg";
const cache = localStorage.getItem(cacheKey);
if (cache) {
const cacheData = JSON.parse(cache);
if (Date.now() < cacheData.expires && cacheData.query === query) {
return cacheData.url;
}
}
const response = await fetch(
`https://api.pexels.com/v1/search?query=${query}&per_page=1`,
{ headers: { Authorization: "您的 API 密钥" } }
);
const limit = response.headers.get("X-Ratelimit-Limit");
const remaining = response.headers.get("X-Ratelimit-Remaining");
const reset = new Date(Number(response.headers.get("X-Ratelimit-Reset")) * 1000);
console.log(`总配额: ${limit}, 剩余: ${remaining}, 重置时间: ${reset}`);
if (remaining === "0") {
console.warn("Pexels API 配额已用尽,将于", reset, "重置");
return "默认图片 URL";
}
const data = await response.json();
const url = data.photos[0]?.src.original || "默认图片 URL";
localStorage.setItem(cacheKey, JSON.stringify({
expires: Date.now() + 1000 * 60 * 60,
url,
query,
}));
return url;
}
async function initDynamicBackground() {
const { latitude, longitude } = await getLocation();
const weatherData = await getWeather(latitude, longitude);
const weatherDesc = getWeatherDescription(weatherData.weatherCode);
const timeOfDay = getTimeOfDay(weatherData.sunrise, weatherData.sunset);
const bgUrl = await getBackgroundImage(weatherDesc.en, timeOfDay.en);
document.body.style.backgroundImage = `url(${bgUrl})`;
document.getElementById("weather").textContent = weatherDesc.zh;
document.getElementById("sunrise").textContent = weatherData.sunrise;
document.getElementById("sunset").textContent = weatherData.sunset;
}
// 执行初始化
initDynamicBackground();
HTML 结构(示例):
<!DOCTYPE html>
<html>
<head>
<title>动态背景博客</title>
<style>
body { background-size: cover; color: white; font-family: Arial; }
#weather, #sunrise, #sunset { margin: 10px; }
</style>
</head>
<body>
<div>天气: <span id="weather"></span></div>
<div>日出: <span id="sunrise"></span></div>
<div>日落: <span id="sunset"></span></div>
<script src="https://unpkg.com/openmeteo@1.1.2/dist/openmeteo.umd.js"></script>
<script src="your-script.js"></script>
</body>
</html>
注意事项
Pexels API 配额:
- 每月配额通过
X-Ratelimit-Limit
查看(通常为 20,000 次)。 - 剩余请求数通过
X-Ratelimit-Remaining
获取。 - 重置时间通过
X-Ratelimit-Reset
(UNIX 时间戳)确定。 - 若配额用尽,建议使用默认图片或暂停请求直到重置。
- 每月配额通过
API 密钥安全:不要将 Pexels API 密钥硬编码在前端代码中。建议通过环境变量或后端代理加载。
缓存优化:背景图片缓存为1小时,避免频繁请求,同时保持内容新鲜,也能减少API请求次数,防止耗尽配额
错误处理:
- 位置获取失败时使用默认经纬度。
- 图片请求失败或无结果时使用默认图片。
- 检查 API 配额,防止超限。
框架无关:代码使用原生 JavaScript,可轻松适配 Vue、React 或其他框架。只需将数据绑定到框架的状态管理(如 Vue 的
ref
或 React 的useState
)。
结语
通过以上步骤,你可以为博客首页实现一个动态功能:根据用户位置显示天气、日出日落时间,并加载匹配的背景图片。核心逻辑使用纯 JavaScript 实现,灵活适用于各种项目。Pexels API 的配额检查确保了请求的可靠性,而缓存机制优化了性能。快试试吧,为你的博客增添一份个性化的魅力!