移动端适配实战:一个天气卡片如何同时适配手机、平板、PC
某天想做一个简单的天气查询页面,顺便练一下移动端响应式布局。要求:手机上看是单列卡片,平板和 PC 上自动调整宽度和字体,不依赖框架,只用 CSS 媒体查询。
最后选了和风天气的免费 API,前端用原生 HTML/CSS/JS,再加 localStorage 保存最近搜索的城市。
技术点
- 媒体查询
@media适配三种屏幕宽度 - 卡片式 UI,flex 布局
- 调用第三方天气 API(需申请 key)
- localStorage 存储历史城市
完整代码
创建一个 index.html 文件,内容如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>天气卡片 - 适配手机/平板/PC</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
background: #f0f2f5;
padding: 20px;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
/* 卡片容器 */
.weather-card {
background: white;
border-radius: 32px;
box-shadow: 0 8px 20px rgba(0,0,0,0.08);
padding: 24px;
transition: all 0.2s ease;
width: 100%;
}
/* 手机尺寸(默认) */
.weather-card {
max-width: 100%;
}
/* 平板:宽度 >= 600px */
@media (min-width: 600px) {
.weather-card {
max-width: 500px;
padding: 32px;
}
body {
padding: 40px;
}
.city-name {
font-size: 2rem;
}
.temp {
font-size: 3.5rem;
}
}
/* PC:宽度 >= 1024px */
@media (min-width: 1024px) {
.weather-card {
max-width: 650px;
padding: 40px;
}
.city-name {
font-size: 2.5rem;
}
.temp {
font-size: 4rem;
}
}
.city-name {
font-size: 1.8rem;
font-weight: 600;
margin-bottom: 8px;
}
.temp {
font-size: 3rem;
font-weight: 300;
margin: 16px 0 8px;
}
.condition {
font-size: 1.2rem;
color: #555;
margin-bottom: 16px;
}
.search-area {
margin-top: 24px;
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.search-area input {
flex: 1;
padding: 12px 16px;
border: 1px solid #ddd;
border-radius: 40px;
font-size: 1rem;
}
.search-area button {
background: #007aff;
color: white;
border: none;
padding: 12px 24px;
border-radius: 40px;
font-size: 1rem;
cursor: pointer;
}
.history {
margin-top: 20px;
font-size: 0.9rem;
color: #666;
}
.history button {
background: #e9ecef;
border: none;
padding: 6px 12px;
border-radius: 20px;
margin-right: 8px;
margin-top: 8px;
cursor: pointer;
}
.error {
color: #d32f2f;
margin-top: 12px;
}
</style>
</head>
<body>
<div class="weather-card" id="weatherCard">
<div class="city-name" id="cityName">加载中...</div>
<div class="temp" id="temp">--°C</div>
<div class="condition" id="condition">--</div>
<div class="search-area">
<input type="text" id="cityInput" placeholder="输入城市名,如 Beijing" value="Beijing">
<button id="searchBtn">查询</button>
</div>
<div class="history" id="historyDiv">
<strong>最近搜索:</strong><span id="historyList"></span>
</div>
<div id="errorMsg" class="error"></div>
</div>
<script>
// 和风天气 API (免费版需申请 key)
// 申请地址:https://dev.qweather.com/
const API_KEY = 'YOUR_API_KEY_HERE'; // 替换成你自己的 key
// 默认城市
let currentCity = 'Beijing';
// localStorage 最近搜索(最多5个)
function getHistory() {
const stored = localStorage.getItem('weather_history');
if (stored) {
return JSON.parse(stored);
}
return [];
}
function saveHistory(city) {
let history = getHistory();
// 去重,并放到最前
history = history.filter(c => c !== city);
history.unshift(city);
if (history.length > 5) history.pop();
localStorage.setItem('weather_history', JSON.stringify(history));
renderHistory();
}
function renderHistory() {
const history = getHistory();
const container = document.getElementById('historyList');
if (!container) return;
container.innerHTML = '';
history.forEach(city => {
const btn = document.createElement('button');
btn.textContent = city;
btn.onclick = () => {
document.getElementById('cityInput').value = city;
searchWeather(city);
};
container.appendChild(btn);
});
}
async function searchWeather(city) {
const errorDiv = document.getElementById('errorMsg');
errorDiv.innerText = '';
if (!city.trim()) return;
// 和风天气 API:先获取城市ID,再获取天气(此处简化:直接用城市名请求实时天气,需使用城市搜索)
// 为简化示例,这里使用 7Timer 或其他更简单的免费 API?但保证真实可用的方案是:改用 OpenWeatherMap 免费版
// 为避免 API 失效,这里改用 OpenWeatherMap 的示例(需要 key)
// 真实场景中申请自己的 key。下面演示结构,实际运行需替换真实接口。
// 注意:免费天气 API 通常有跨域限制,需要后端代理或使用 JSONP。这里为了演示前端适配,假设已经处理好跨域。
// 由于时间关系,本文只写出逻辑骨架,实际使用时填上你自己的 API 地址。
// 参考:OpenWeatherMap 付费免费层:https://openweathermap.org/current
const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&units=metric&lang=zh_cn`;
try {
const response = await fetch(url);
if (!response.ok) throw new Error('城市不存在或API限制');
const data = await response.json();
document.getElementById('cityName').innerText = data.name;
document.getElementById('temp').innerHTML = `${Math.round(data.main.temp)}°C`;
document.getElementById('condition').innerText = data.weather[0].description;
saveHistory(city);
currentCity = city;
} catch (err) {
errorDiv.innerText = '查询失败:' + err.message;
console.error(err);
}
}
// 初始化
function init() {
renderHistory();
const defaultCity = getHistory()[0] || 'Beijing';
document.getElementById('cityInput').value = defaultCity;
searchWeather(defaultCity);
document.getElementById('searchBtn').onclick = () => {
const input = document.getElementById('cityInput').value.trim();
if (input) searchWeather(input);
};
document.getElementById('cityInput').addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
const input = document.getElementById('cityInput').value.trim();
if (input) searchWeather(input);
}
});
}
init();
</script>
</body>
</html>说明
- 媒体查询:手机默认卡片宽度 100%(边距 20px);平板(≥600px)卡片限制最大 500px,字体放大;PC(≥1024px)卡片最大 650px,进一步放大字体。你可以在浏览器开发者工具切换到设备模拟模式测试。
- 天气 API:示例中使用了 OpenWeatherMap 的 API,需要申请免费 API Key,替换
YOUR_API_KEY_HERE。和风天气也类似,但可能涉及跨域问题,建议用 OpenWeatherMap。 - localStorage:保存最近 5 个搜索的城市,点击历史按钮快速查询。
- 跨域问题:免费天气 API 大多允许前端直接调用(CORS 已开放),如果遇到跨域报错,可以考虑用免费的 JSONP 服务或自建后端代理。但作为本地演示,可以直接双击
index.html运行(前提是 API 支持 CORS)。
遇到的坑
1. 移动端点击输入框自动放大
通过 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes"> 中设置 user-scalable=yes 允许缩放,但输入框字体小于 16px 会导致 iOS 自动放大。解决:设置 input { font-size: 16px; }(已隐含在样式中)。
2. 媒体查询断点选择
常用断点:600px(平板竖屏)、1024px(PC)。实际可根据需要调整。
3. API 请求跨域
很多天气 API 免费版不支持浏览器直接调用。可以改用支持 CORS 的服务(如 OpenWeatherMap 的 https://api.openweathermap.org/data/2.5/weather 支持跨域)。如果不行,可以使用代理如 https://cors-anywhere.herokuapp.com/,但不建议在生产使用。
4. localStorage 读取时机
需要在页面加载时读取并渲染历史按钮,否则点击无效。
总结
这个页面纯前端,不依赖框架,核心是 CSS 媒体查询 + flex 布局。适配三种常见屏幕宽度,同时演示了 localStorage 和 fetch API。
实际使用时,只需替换成你自己的天气 API Key,放到任意静态服务器(或本地打开)即可演示。