ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

从零构建天气可视化页面:数据获取、ECharts绘图与部署实战

从零构建天气可视化页面:数据获取、ECharts绘图与部署实战 1. 项目背景与设计思路1.1 为什么突然想做一个天气可视化页面前几天有个朋友在整理个人主页问我除了贴几张照片、写几行自我介绍之外还能放点什么让页面看起来不那么空。我第一反应就是放一个天气可视化模块进去。出门之前看一眼自己做的页面温度和风力趋势一目了然又实用又能秀技术。聊完之后我自己也手痒直接在本地把这个功能从头到尾完整写了一遍从拿数据、出图表、调布局到部署上线一套流程走完中间踩了不少坑也整理了一堆有用的细节。当时我心里盘算得很清楚这不能只是调一个 API 然后把数字列出来那就太没意思了。我要做一个真正有展示效果的天气可视化模块包括实时天气卡片、24 小时温度趋势图、未来 7 天预报、湿度变化曲线再加上城市搜索和响应式适配。让这个页面既能放在个人网站当门面也能作为一个独立小工具日常使用。做完之后回头再看这个项目的价值不在调用接口这一步而在把零散的数据变成一套完整的、直观的视觉语言。这也是我想在文章里重点讲清楚的事情不是教你怎么抄代码而是带着你重新走一遍设计和实现的过程知道每一步为什么这样做。1.2 这个项目适合谁来参考这个项目特别适合下面几类人前端刚开始入门、做过静态页面但没接触过 API 数据请求的人可以通过这个项目完整走一遍请求数据—处理数据—渲染页面的流程。想做个人主页、个人仪表盘但缺一个亮点模块的开发者。对数据可视化感兴趣想用 ECharts 做点实际东西又不想一上来就啃大而全的图表后台的人。需要快速搭建一个内部工具页面、运维监控面板但暂时不想引入重型框架的场景。它不需要太高的门槛懂一点 HTML、CSS、JavaScript 的基础就够用了。如果你用的是 Vue/React 技术栈也可以把文中的思路平移过去核心逻辑是一样的。1.3 整体设计思路我先把这个项目拆成了三个层次第一层是数据层解决天气数据从哪来的问题。市面上天气 API 不少要选一个国内访问快、免费额度够用、返回字段清晰的。我测试过多个数据源之后决定以和风天气作为主数据源后面会详细对比。第二层是渲染层解决数据怎么展示的问题。我选择了ECharts作为图表核心库因为它的中文文档完整、配置项丰富折线图、柱状图、雷达图都能轻松搞定而且社区案例多遇到问题搜索起来也快。第三层是交互层解决用户怎么用的问题。这个页面不是一个死板的报表而是让用户能输入城市、切换视图、自适应手机和电脑的实用工具。我用纯原生 JavaScript 完成不依赖 Vue/React 脚手架这样显得轻薄部署起来也非常省事放到任意一台支持静态页面的服务器上就能跑。2. 技术选型与方案对比2.1 天气数据源怎么选“天气数据从哪来”是我最早确认的问题因为数据源选得不好后面全白搭。市面上能用的天气 API 大概有这几个候选和风天气、OpenWeatherMap、心知天气、高德地图天气组件。我直接做了一个对比表格方便你根据自己的情况选数据源国内访问速度免费额度返回字段注册复杂度综合推荐度和风天气快每天有一定免费请求量实时、24h、7天、空气质量、分钟级降水简单需实名首推OpenWeatherMap一般有时超时免费版限 60 次/分钟实时、4 天/16 天预报邮件验证备选心知天气快免费版请求次数较少实时、3 天预报简单一般高德天气组件快跟随高德 JS API 使用实时、4 天预报简单一般我最后选了和风天气原因有三个国内服务器和本地网络访问它的接口都非常稳响应基本在 100ms 左右不会出现 OpenWeatherMap 那种时不时超时的情况。它有一个独立的 GeoAPI 接口可以把城市名解析成对应的 LocationID做城市搜索非常方便。免费版提供的字段足够丰富实时天气、24 小时逐小时预报、7 天预报都能覆盖而且每一个接口都有明确的免费配额。这里提醒一下和风天气注册后需要创建一个项目然后会拿到一个 API Key。这个 Key 在开发阶段可以直接写在前端代码里但要发布到公网的话建议做一个后端代理或者换成专门的前端 Key避免被人盗刷。这点我后面还会详细说。2.2 可视化库为什么选 ECharts可视化库我用过不少从最原始的 Canvas 手绘到 Chart.js、ECharts、AntV G2甚至 D3.js 都折腾过。这次的项目我毫不犹豫选了 ECharts。理由很现实ECharts 的折线图、面积图、柱状图开箱即用不需要自己去算坐标轴、刻度、网格。配置项结构非常稳定官方文档有大量在线示例复制下来改一改就能用。它对数据的格式化、时间轴处理、tooltip 提示都做得很完善能省下大量开发时间。中文社区非常活跃遇到为什么我的图表不显示这种问题搜索一下都有现成答案。如果用 Chart.js它的优点是更轻量、体积更小但在做复杂坐标系、多图表联动的场景下配置方式反而没有 ECharts 直观。D3.js 虽然自由度最高但学习曲线过于陡峭一个天气可视化页面用 D3 属于杀鸡用牛刀。所以我的结论是ECharts 是这种项目里性价比最优的选择。2.3 前端技术栈与项目形态我这次没有用 Vue 或者 React而是选择了纯静态页面 原生 JavaScript ES6 模块的方案。原因有这几点项目本身不复杂没有复杂的状态管理需求用框架反而多一层抽象。纯静态页面可以直接扔到 GitHub Pages、Vercel、Nginx 上部署成本极低。单文件里的逻辑更容易被初学者读懂不会因为是某一个组件内的生命周期问题导致排查困难。目录结构我设计得很简单weather-viz/ ├── index.html ├── css/ │ └── style.css ├── js/ │ ├── config.js │ ├── api.js │ ├── charts.js │ └── main.js └── assets/ └── icons/config.js统一存放 API Key、接口地址、默认城市api.js负责所有网络请求charts.js封装 ECharts 图表的创建和更新main.js负责页面逻辑编排。这样拆的好处是以后想替换数据源或者增加图表不用动整个页面的代码。3. 核心功能与实现细节3.1 天气数据结构解析在写代码之前我花了不少时间研究天气 API 返回的数据结构。和风天气的实时天气接口返回结构大概是下面这样{ code: 200, now: { temp: 28, feelsLike: 30, text: 多云, windDir: 东南风, windScale: 3, windSpeed: 15, humidity: 60, pressure: 1003, vis: 16, cloud: 10 } }刚开始接触天气数据的人很容易对这些字段一头雾水我来逐个讲一下temp是当前温度单位是摄氏度。feelsLike是体感温度它会考虑湿度、风速等因素夏天和冬天的体感差距很明显。text是天气现象描述比如晴、多云、小雨这是展示时最直观的一个字段。windDir和windScale分别表示风向和风力等级做出行判断时非常有用。humidity是相对湿度百分比数值。press是大气压强单位 hPa。vis是能见度单位公里。cloud是云量范围 0 到 100。24 小时预报接口返回的是一个数组每个元素包含小时时间戳、温度、天气现象、湿度、风力等信息。7 天预报则包含每一天的最高温、最低温、白天和夜间的天气现象。我最终选用了这四组数据实时天气做卡片展示24 小时温度做折线图7 天预报做每日温度区间展示湿度做柱状图。3.2 城市搜索与 LocationID 获取和风天气的天气接口不是直接接收北京这种中文名它需要一个 LocationID比如北京是101010100。所以要实现城市搜索必须先用 GeoAPI 把城市名转换成 ID。我写了一个getLocationId函数// geoapi.qweather.com 城市搜索接口 async function getLocationId(city) { const url https://geoapi.qweather.com/v2/city/lookup?location${encodeURIComponent(city)}key${API_KEY}; const res await fetch(url); const json await res.json(); if (json.code 200 json.location.length 0) { // 取第一个匹配结果一般情况下就是目标城市 return json.location[0].id; } throw new Error(未找到该城市); }这里有几个细节要提醒一定要对city参数做encodeURIComponent编码不然输入中文时会请求失败。返回结果是一个数组搜索北京可能同时匹配到多个地方我默认取第一个因为官方把最匹配的放在最前面。接口调用失败时要抛异常而不是静默返回方便在页面里统一提示用户。城市搜索框我做了 300ms 的防抖处理避免用户每敲一个字母就发一次请求。实现方式也简单就是 setInterval 的清理和重设searchInput.addEventListener(input, (e) { clearTimeout(timer); timer setTimeout(() { handleCitySearch(e.target.value); }, 300); });实测下来防抖之后不仅请求量大幅下降输入体验也流畅了不少。3.3 实时天气卡片与天气图标映射拿到实时天气数据后我把它渲染成一张卡片。卡片上展示温度、体感温度、天气现象、湿度、风向、风力这六个最常用的指标。为了不让页面显得干巴巴我给每一种天气现象配了一个对应的图标。和风天气的text字段是中文描述直接用它做 icon 映射键例子如下const weatherIconMap { 晴: sunny, 多云: cloudy, 阴: overcast, 小雨: light-rain, 中雨: moderate-rain, 大雨: heavy-rain, 雷阵雨: thunderstorm, 小雪: light-snow, 雾: fog };图标我准备的是 SVG 文件放在assets/icons目录下。这样做的优势是 SVG 清晰度高放大缩小不糊而且体积小。如果你不想自己做图标完全可以用 ECharts 自带的图形或者直接显示中文字符加上联想色块比如晴天用橙色背景、雨天用蓝色背景效果也直观。这一块我额外做了一件事根据天气现象自动改变页面主色调。晴天是偏暖橙色的底色阴天是灰蓝色雨天是深蓝色。用 CSS 变量来控制切换主题只需要修改页面上body的几个 CSS 变量即可body.sunny-theme { --bg-color: #fef3c7; --card-bg: #ffffff; --text-color: #1f2937; } body.rain-theme { --bg-color: #e0f2fe; --card-bg: #ffffff; --text-color: #1e293b; }这个小设计非常加分用户打开页面的一瞬间就能感受到天气氛围。3.4 ECharts 图表配置实战接下来是核心部分用 ECharts 绘制温度趋势和湿度柱状图。我先把图表初始化的代码封装成一个通用方法function createChart(containerId) { const dom document.getElementById(containerId); if (!dom) return null; const chart echarts.init(dom); return chart; }创建好实例之后温度趋势图的配置我写成这样function renderTempChart(chart, hourlyData) { const hours hourlyData.map(item item.fxTime.slice(11, 16)); const temps hourlyData.map(item Number(item.temp)); chart.setOption({ tooltip: { trigger: axis }, grid: { left: 40, right: 20, top: 30, bottom: 30 }, xAxis: { type: category, data: hours, boundaryGap: false }, yAxis: { type: value, name: 温度(°C) }, series: [ { name: 温度, type: line, data: temps, smooth: true, showSymbol: false, lineStyle: { width: 3, color: #f97316 }, areaStyle: { color: { type: linear, x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: rgba(249, 115, 22, 0.3) }, { offset: 1, color: rgba(249, 115, 22, 0) } ] } } } ] }); }这里有几个值得记下来的配置细节showSymbol: false让数据点不显示圆点线条看起来更平滑干净。smooth: true开启曲线平滑处理温度趋势会更自然。areaStyle用渐变色视觉上有一个温度区域的纵深感比单纯的折线更有层次。boundaryGap: false让折线从坐标轴开始不会留出多余空白。24 小时预报的时间字段是 ISO 格式字符串比如2025-06-14T18:0008:00我用slice(11, 16)截取出18:00简单直接不需要引入 moment.js 之类的库。3.5 未来 7 天预报与双柱展示7 天预报我用的是双柱图而不是折线图。每天一根高温柱和一根低温柱并列展示对比效果非常明显。编码的时候我用了一个小技巧把高温和低温分别作为两个 series然后在同一根 category 轴下设置两个柱的宽度和间距。核心配置如下series: [ { name: 最高温, type: bar, data: highTemps, itemStyle: { color: #ef4444, borderRadius: [4, 4, 0, 0] }, barMaxWidth: 14 }, { name: 最低温, type: bar, data: lowTemps, itemStyle: { color: #3b82f6, borderRadius: [4, 4, 0, 0] }, barMaxWidth: 14 } ]barMaxWidth这个属性值得一说。如果不设置它柱子会默认拉得很宽两根柱子挤在一起不好看。设置成 14px 之后在手机和电脑上都能保持合适的宽度。再加上borderRadius柱子的顶部会是圆角看起来比直角柱柔和不少。3.6 数据自动刷新策略天气数据是会变的所以页面不能只请求一次就完事。我设置了每 30 分钟自动刷新一次实时天气和 24 小时预报。为什么选 30 分钟而不是 5 分钟因为天气变化本身是一个慢过程频繁刷新既浪费 API 配额也没有实际意义。自动刷新的代码非常简单就是在初始化成功后启动一个定时器async function refreshAll(city) { await loadWeatherData(city); } // 首次加载 refreshAll(101010100); // 每30分钟自动刷新 setInterval(() { refreshAll(currentCityId); }, 30 * 60 * 1000);这里要特别提醒一个坑ECharts 实例更新数据不要重复调用echarts.init而应该复用同一个实例再调用setOption。如果每次都重新 init会导致内存泄漏而且页面上会叠加多个图表实例。4. 实操过程与部署上线4.1 从零搭建项目的完整步骤我按实际操作顺序把搭建过程完整列出来方便你跟着做创建项目根目录weather-viz在里面新建index.html、css、js、assets文件夹。在index.html里引入 ECharts 的 CDN 文件以及自己的config.js、api.js、charts.js、main.js。在config.js中配置 API Key 和默认城市 LocaltionID。在api.js中实现getLocationId、getNowWeather、get24hForecast、get7dForecast四个函数。在charts.js中实现renderTempChart、renderHumidityChart、renderWeekChart三个渲染函数。在main.js中调用这些函数完成页面初始化和事件绑定。用python -m http.server 8080或 VS Code Live Server 打开页面调试。调试通过后部署到 GitHub Pages 或自己的 Nginx 服务器。4.2 完整请求流程的串联main.js里最重要的逻辑是 根据城市 ID 并发请求三类数据然后分别渲染。我用Promise.all并发发起请求而不是串行等待这样可以显著减少整体加载时间。核心代码如下async function loadWeatherData(locationId) { const [nowRes, forecast24hRes, forecast7dRes] await Promise.all([ fetchNowWeather(locationId), fetch24hForecast(locationId), fetch7dForecast(locationId) ]); renderNowCard(nowRes); renderTempChart(tempChart, forecast24hRes.hourly); renderHumidityChart(humidityChart, forecast24hRes.hourly); renderWeekChart(weekChart, forecast7dRes.daily); }这段代码虽然简单但体现了异步编程的一个核心思想相互独立的数据请求完全可以放到一起等待结果而不是写成先请求 A、等 A 完成后再请求 B的嵌套结构。嵌套写法在请求变多之后会变成丑陋的回调地狱用Promise.all就清爽多了。4.3 页面布局与响应式适配布局上我采用了顶部搜索栏 主信息卡片 图表区的经典结构。在桌面端图表区可以并列放两张图在移动端则自动上下堆叠。这个用 CSS Grid 实现非常方便.chart-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; } media (max-width: 768px) { .chart-grid { grid-template-columns: 1fr; } }另一个响应式细节是图表的尺寸。ECharts 默认情况下会根据容器大小自适应但如果父容器一开始是隐藏的或者尺寸为 0图表就会渲染成 0 高度。为了防止这种情况我给每个图表容器都设置了明确的高度比如height: 320px。这里我还做了一个兜底操作在窗口尺寸变化时调用chart.resize()保证图表不会塌陷或变形window.addEventListener(resize, () { tempChart tempChart.resize(); humidityChart humidityChart.resize(); weekChart weekChart.resize(); });4.4 部署到线上这个项目因为是纯静态页面部署相当简单。我直接选择推到 GitHub 仓库然后开启 GitHub Pages。步骤是新建仓库把代码推上去。进入仓库 Settings → Pages。选择部署分支为main目录为根目录。保存后等一两分钟就会生成一个https://用户名.github.io/仓库名/地址。如果你有独立域名或者自己的服务器也可以直接在 Nginx 的静态目录里配置server { listen 80; server_name weather.example.com; root /var/www/weather-viz; index index.html; }部署完成后页面就可以通过公网访问了。要注意的是如果 API Key 是你自己的且绑定了付费套餐建议还是走一个简单的后端代理把 Key 隐藏起来避免被其他人直接扒走调用。5. 常见问题与排查技巧5.1 图表不显示或者高度为 0这是我见到最多的问题。排查思路按照优先级排列打开浏览器开发者工具看有没有红色报错。检查图表容器是否存在id是否拼写正确。重点检查容器的 CSS 高度。ECharts 的图必须在有明确高度的容器里才能渲染如果容器高度是0或者父级高度没有撑开图表就是一片空白。相对稳妥的做法是给图表容器直接写height: 360px而不是依赖父级撑开。如果你用的是 flex 布局还要检查是不是被flex: 1挤压了高度。5.2 跨域请求失败和风天气的接口默认允许跨域请求所以一般不会遇到 CORS 问题。但如果你用的是某些需要服务端代理的 API就需要注意了。浏览器的跨域报错长这样Access to fetch at https://api.example.com/... from origin http://localhost:8080 has been blocked by CORS policy遇到这种情况解决办法有这么几种本地开发时用Vite或者webpack-dev-server自带的代理功能把/weather路径代理到真实接口地址。线上部署时让后端或云函数转发请求在服务端把响应头加上Access-Control-Allow-Origin: *。如果你没有后端也可以找一个支持 CORS 的第三方代理服务但这种方案稳定性差不推荐正式使用。5.3 API Key 泄露和盗刷风险在纯前端项目里API Key 本质上是无法完全隐藏的即使你写在 JS 文件里别人只要打开开发者工具就能看到。所以我的建议是如果只是个人学习项目API Key 放在前端是没问题的注意别把它推到公开仓库里万一暴露了就立刻去控制台重置。如果是正式项目部署一个极简的云函数比如 Vercel Serverless 函数作为中转前端请求自己的函数域名由函数去调用天气 API 并返回结果。这样 Key 就一直留在服务端。这里有一个安全细节哪怕你用云函数中转也应该为前端请求加一个简单的身份校验比如带一个自定义请求头或者短期 token防止别人把你云函数当作公开代理来刷接口。5.4 时间戳和时区问题和风天气返回的小时预报时间字段是带时区偏移的例如2025-06-14T18:0008:00。我在截取时间的代码里直接用字符串分割这么做在本机时区为东八区时没问题但如果你的服务器或者用户浏览器配置了其他时区直接用字符串切割就会得到错误的小时。比字符串切割更稳妥的做法是用Date对象加getHours()const date new Date(item.fxTime); const hour date.getHours();但new Date(2025-06-14T18:0008:00)最终得到的是本地时间对应的时间戳用户在其他时区打开页面显示的也是本地时间。具体怎么取舍要看产品需求。如果是面向国内用户直接按东八区显示是没问题的如果要国际化就得调用toLocaleString做时区转换。5.5 ECharts 数据更新重复叠加另一个经典问题用了setOption之后旧数据没被清掉新数据直接叠加上去出现了多条线或者多个数据系列。这个是因为 ECharts 的setOption默认是合并模式。如果你之前的 series 数量是 2 个新数据只有 1 个那旧的 series 可能还留着。解决办法是加一个参数chart.setOption(option, true);第二个参数true表示不再和之前的配置合并直接替换。不过要注意这样替换会把图表的其他交互状态也重置掉如果只想更新数据更精确的做法是用chart.setOption({ series: [{ data: newData }] })这种局部更新的写法。6. 项目延伸与个人经验整个项目做完之后我最大的感受是天气可视化这个需求看起来小实际做起来需要考虑的细节远超预期。你以为自己只是在画一条温度曲线实际还要处理数据格式、异步并发、用户交互、响应式布局、部署上线、安全防护。这一套流程跑完基本就是一次完整的前端小项目实践。我建议你在这个基础上尝试几个方向增加一个温度区间建议模块根据当天的最高温、最低温自动给出穿衣建议或者出行提醒。这个逻辑不复杂但对非技术用户非常友好。把图表从 ECharts 迁移到 Canvas 手写或者换成更现代的图表库对比一下不同方案的性能和代码量。接入更多数据源比如把空气质量指数和风速叠加在同一个展示面板上做成一个综合环境仪表盘。把代码用 TypeScript 重写一遍顺带熟悉一下类型定义对前端项目的帮助。如果你在自己做的过程中遇到了什么奇怪的问题或者有更好的天气可视化思路欢迎在评论区聊一聊。我个人很想知道大家会如何把这种小工具玩出更多花样。
返回列表