0%

web天气卡片开发记录

成品网站可以在这里查看(请使用手机)

0.前言

10.4晚上,写完高数线代作业终于空下来了,出了趟门后准备开始写助手的任务。

第一项搭建个人博客的任务暑假刚好做完,省事了。我选择第二项制作天气卡片的任务。因为要求把开发过程做一篇记录发表,于是就有了这篇文章。

1.数据源

拿到手的时候任务介绍里指路了可以使用高德或者彩云的天气api,但是恰逢国庆,彩云开放平台审核放假了,一直是Pending Review的状态,于是我转向高德。

高德的api很简单,一下子就弄懂,可惜它提供的更多是地图方面的服务,天气数据实在太有限了,不足以完成进阶任务。找了一晚上,选定了和风天气API,对于个人开发者的优待完全满足了我的需求。
定价
认证个人开发者有也非常快,应该是采用了AI审核,我凌晨发出申请,半分钟后就通过了,太感动了。
审核成功邮件
可靠的数据源到手,着手开始制作。

2.构思

  1. 任务建议使用原生js实现,且没有指定平台。鉴于助手在手机端更常用,而且时间很有限不足以完成响应式布局,我决定开发手机页面,这样可供参考的软件也会很多。
  2. 网络请求: 决定使用axios,因我没用过,想试试。
  3. 天气图标: 和风天气设计了一套开源的和风天气图标,仓库地址在这
    和风天气图标
    不过它只设计了天气现象相关图标,缺少诸如大气压、紫外线、日出、能见度等图标,但是先凑活着用吧。
  4. 图标展示: 推荐的Charts.js文档实在难看懂,网上搜索不到几篇教程。最后选择了Apache基金会的ECharts.js,不仅有中文文档,网上能搜到的教程也多得多。不过全功能的包体有点大,等以后可以研究一下按需引入。
  5. 页面布局:我结合了手头的华为天气和数据提供商自己的一个S和风天气app

3.动手

3.0 IDE选择

以前开发我基本就用DW或者vsc + 浏览器内建的样式编辑器,这次想尝试一下JetBrians WebStorm。

一是听说它的代码补全很强,二是杭电学生认证后有正版的全家桶可以白嫖。

3.1 简易搭建一个本地缓存

  1. 修改CSS的时候如果实时预览,每次修改保存都要重载页面,向api获取数据,不仅耗时而且浪费很多的使用次数,于是用Python在本地创建了一个服务器,作用就是读取预先保存的json再返回。测试的时候只要用本地的api就好了,反正不需要最新数据。
  • 需要注意的是,即使json和py文件都是utf8编码,也应该指定一下open函数的打开编码,比如在我这就默认用gbk打开了,显示“’gbk’ codec can’t decode byte 0xb4 in position 179: illegal multibyte sequence”。

  • 解决方案:在open()函数里传递encoding=’utf-8’的参数,例如:

    1
    with open('now.json',encoding='utf-8') as f:
  1. 由于WebStorm的本地调试服务器和Python的临时数据服务器不在一个端口上,请求会因为跨域被阻止。为了测试需要,就要设置响应头。

跨域请求被阻止

在Sanic中,中间件(Middleware)功能可以在请求到达路由的前后处理数据,以此来实现用户自定义的全局功能。在这里,因为需要进一步处理相应,所以我们将中间件绑定到“response”上。这里使用装饰器的形式实现。

1
@app.middleware('response')

然后,我们自定义一个函数设置服务器允许任何域访问资源(临时调试用,在生产环境请严格指定域来避免安全风险)。

1
2
async def fkCORS(request, response):
response.headers["Access-Control-Allow-Origin"] = "*"

现在整个中间件看起来是这样的

1
2
3
@app.middleware('response')
async def fkCORS(request, response):
response.headers["Access-Control-Allow-Origin"] = "*"

MDN上的“Access-Control-Allow-Origin”
Sanic 入门:中间件(Middleware)

  1. Sanic的服务器会绑定主机名称,如果我们的局域网ip为10.66.233.77,但设置服务器host为localhost,port为8090,则不能通过10.66.233.77:8090访问到网站,只会显示拒绝连接。
    host不对应导致拒绝连接
    如果想要用局域网设备访问,比如使用手机真机调试,我们应将host设置为局域网ip地址来规避这个问题
    1
    2
    if __name__ == '__main__':
    app.run(host='10.66.233.77',port=8090,debug=True)

注意,如果你的局域网ip地址不是固定的,记得手动在变更后修改。

3.2 使用css计算器生成线性背景渐变图

制作过程中为了顺畅衔接地图和天气卡片,需要给卡片所在容器设置一个由透明到渐变再到完全纯色的背景。同时卡片本身就需要一个渐变色。

这里就用到了“linear-gradient()” 这个函数。

linear-gradient() 函数用于创建一个表示两种或多种颜色线性渐变的图片。
注意,因为是图片,所以只能被用于图片可以被使用的地方,而并不适用于background-color等属性

卡片的渐变比较简单,从只需要从左下角颜色渐变到右上的颜色即可:

1
2
3
#overviewCard{
background-image: linear-gradient(to top right, #1C1D1F 0%, #353E4D 100%);
}

在背景用例中,我需要一部分纯透明区域,一部分渐变区域,一部分纯色区域。这些区域的范围是可以手动指定的,像这样:

1
2
3
#overviewContainer{
background-image: linear-gradient(to bottom, transparent 0%, transparent 30%, #FFFFFF 50%, #FFFFFF 100%);
}

实际效果如下:
css计算背景渐变.png

3.3 使用三目运算符动态分配类名

在24小时预报和7天预报中,为了将相邻两个容器区分开来增加可读性,需要在生成html的时候动态指配两种class
刚好上周的C语言课程学到了三目运算符,可以迁移到js上来(在js里似乎叫三元运算符或者条件运算符)。

因为具体用法相同,在C语言的课程上讲过,就不再做解释了,相关代码如下

1
2
3
4
5
6
7
let html = "";
for (let i = 0;i < Object.keys(foo).length;i++){ //Object.keys().length用来获取json中的项数
html += `<div class="${(i % 2 == 0)?"dark":"light"}"> //区分两种容器的颜色
<div>...</div>
...
</div>`;
}

实际效果如下:
三元运算符区分容器

3.4 使用css var()函数实现暗黑主题

不管是什么主题,整个网站都会遵循一定的配色标准,简单来说就是分门别类,不同作用的元素对应了不同的颜色。
当我们启用暗黑模式的时候,同一作用的元素需要切换颜色。

如何不用写两套css就能实现这种效果呢?我们可以用var()函数配合相应变量替代掉所有颜色区域。
相应的颜色可以定义在根元素节点上,比如<html>标签中,比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
:root{
--tb: #000000; /*正常为黑*/
--tw: #FFFFFF; /*正常为白*/
--tt: #CCCCCC; /*tip提示语*/
--qicon: #000000; /*天气图标*/
--bgc: #FFFFFF; /*背景色*/
--bgg: #CCCCCC; /*背景分割色*/
--bgd: #f0f0f0; /*背景区分色*/
--bge1: #1C1D1F; /*背景强调色*/
--bge2: #353E4D;
}
.dark{
--tb: #E0E0E0;
--tw: #FFFFFF;
--tt: #777A81;
--qicon: #E0E0E0;
--bgc: #1C1D1F;
--bgg: #2A2F35;
--bgd: #2C2D31;
--bge1: #1C1D1F;
--bge2: #353E4D;
}

其中:root 选择器匹配文档根元素。在网页中,根元素始终是<html>元素。
然后在需要切换颜色的地方,都应该显式指定相应的颜色变量:

1
2
3
4
5
6
7
8
9
#overviewCard{
background-image: linear-gradient(to top right, var(--bge1) 0%, var(--bge2) 100%);
}
#overviewSummary{
color: var(--tb);
}
#detailContainer{
background-color: var(--bgg);
}

现在我们看到,默认情况下浏览器使用:root定义的颜色组:
:root颜色组被启用
当我们给<html>添加类”dark”的时候,dark颜色组覆写了root颜色组的设置,<html>里的元素都会继承dark的变量设定:
:root颜色组被启用
这样就能实现颜色切换了。

3.5 动态切换图表的配色

很遗憾,解决了基本元素的配色问题,我们还需要单独解决图表配色。因为图表的颜色设定实际上是写在js的json中传递给函数的。
相关设置可以在ECharts的配置项手册中很详尽地看到,这里不再赘述
因为图标在创建后支持动态更改设定,所以我们使用darkCharts()和lightCharts()两个函数交替切换图表样式,缺省设置会保留之前的,但是要求格式完全一致
下面是部分实现代码,可以看到非常难以阅读(这里我使用变量来代替所有相同部分以便后期快速更改):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
function darkCharts(){
let textColor = "#E0E0E0";
let lineColor = "#454545";
let borderColor = "#000000";
let borderWidth = 1;
foreDaysChart.setOption({
yAxis: [
{
splitLine:{
lineStyle: {
color: [lineColor],
}
},
axisLabel:{
textStyle: {
color: textColor,
textBorderColor: borderColor,
textBorderWidth: borderWidth,
}
}
}
],
xAxis: [
{
axisLabel:{
textStyle: {
color: textColor,
textBorderColor: borderColor,
textBorderWidth: borderWidth,
}
}
}
],
series: [
{
label: {
color: textColor,
textBorderColor: borderColor,
textBorderWidth: borderWidth,
}
},
{
label: {
color: textColor,
textBorderColor: borderColor,
textBorderWidth: borderWidth,
}
},
]
})
}

吐槽:在新版的更新日志里已经不建议我使用textBorderColor和textBorderWidth两个属性,然而它并没有提供合适的替代属性,所以还是只能用着。害得我找了好久更优解。

3.6 模拟$(document).ready()与初始填充内容

没时间做鱼骨屏了,而且提到的方便工具都在vue或者react体系下,我也用不了。

于是我准备直接在页面加载时初始化一系列空数据,有总比没有强。然后在获取到具体数据以后或替换或更新。

这里我发现,原生的js并没有提供像jQuery库一样方便的现成函数$(document).ready()。
但是我们可以通过判断document.readyState来自己造一个轮子

当document.readyState变为interactive(可交互)时,就代表html文档已经解析完成,但是诸如图像,样式表和框架之类的子资源仍在加载。实际上这相当于$(document).ready()的状态。

当该属性值发生变化时,会在 document 对象上触发 readystatechange 事件,通过监听这个事件来运行回调函数,我们就可以模拟出一样的效果

代码如下:

1
2
3
4
5
document.onreadystatechange = function () {
if (document.readyState === "interactive") {
...
}
}

3.7 localStorage储存偏好和原生实现类的增删改

这对我而言完全是完全是新东西了,以前只接触cookie。

localStorage是持久化的本地存储,除非是通过js删除,或者清除浏览器缓存,否则数据是永远不会过期的。

而cookie是用于客户端和服务端间的信息传递,每次请求都会被附带在请求头中,因此不适宜储存大量数据或者用户个人偏好。

在这里我用localStorage储存了用户上次使用的主题偏好,可以在每次刷新时自动应用,防止闪瞎。
localStorage的增删改函数是原样从Sukka的博文你好黑暗,我的老朋友 —— 为网站添加用户友好的深色模式支持中抄来和简化的,我就不放代码了。

在简化过程中准备直接给<html>标签设置颜色组属性,结果,很遗憾发现原生js没有现成的封装,于是从网上又抄了一个。
稍微改了下,因为WebStorm疯狂说我代码不规范,变量乱声明。

技术细节到这里就结束了

4.后记

终于写到后记了,写个博文用了我4个小时,累死了(所以博客才万年不更新的)。

  • 这次因为时间仓促,额外发挥的部分要求,比如响应式页面、骨架屏、toast用户提示、webpack打包等没有实现。
  • 整个网页是国庆花了整两天搓出来的,期间学习了ECharts、axios、localStorage、css函数等各种新知识。
  • 我没有预料到新知识的学习成本还是很高的,耗时非常严重。然后在一开始寻找合适api,选择合适图表库的时候走了很长的弯路。
  • 这次是我第一次写出一整个兼具美观和功能的网页,还是很有成就感的(但是真的累,两天速成不可取)
  • 美中不足的是高德地图的JS API竟然没有提供夜间模式的地图,结果开了夜间模式首屏还是瞎眼了,唉
  • 这次是我第一次用原生js来实现一系列功能,感谢You-Dont-Need-jQuery的指导,让我看到了一系列jQuery封装后各种功能本来的样子。有些地方,原生js也能一样简洁!

最后,如果博文有错误或者错字的地方,欢迎指出(其实大概仅限于我认识的人,因为博客没有评论系统)
你可以发送邮件到asak&irain.cc(请把&换成@)
很荣幸能得到你的耐心阅读!