戴兜

戴兜

Coding the world.
github
bilibili
twitter

手撸一个前端天气卡片

image

自己亲手做一个天气卡片组件的想法其实很早就有了,但是做起来难度还是很大的(布局、数据源、天气展示、自适应),最终不了了之。最近学校社团面试题目是做一个天气卡片,正好可以借此机会趁着国庆小长假静下心来好好研究一番。于是就有了今天的这篇文章。

~(实际上在国庆小长假之前就基本上把问题搞定了)~ 目前版本的 DouWeather 托管在临时仓库,因为没有考虑代码结构,后期维护困难。可以 watch 一下这个仓库关注后续进展,我会抽时间重构下代码并在这个仓库更新。

Ⅰ. 设计构思阶段#

1. 灵感来源#

我给 DouWeather(后称 DW)的定位是网页小组件,也是出于这个考虑,我参考了如 iOS 系统的小部件、新版 MIUI 系统小组件、鸿蒙系统小部件、win11 小组件,发现都无一例外具有同一特征:扁平化,圆角,选用无衬线字体,元素风格简洁,并且四者都在或背景或图标中大量使用渐变,使小部件表现得较为灵动。其中 win11 小部件添加了浅阴影,可能是为了让小部件从亚克力背景中凸显出来。

于是乎,我也照猫画虎,设计了 DW 的晴天图标,并且用 XD 设计出了第一种卡片样式(现 DW 的 medium 卡片样式)。

注: DouWeather 的设计也参考了 google adsense (MD2),mew 等网站的设计风格。

image

将 medium 类样式的卡片缩短,便衍生出了一个正方形的 small 样式小组件。

image

之前心血来潮想写天气卡片的时候也做过图,当时参考的 MD 风格,正好趁着国庆回家从电脑里边翻了出来,下面便不知道多久前天气卡片的初稿… 就… 蛮抽象的…

image

2. 图标设计#

DW 中许多要素都~抄~借鉴了小米天气,图标也是如此。DW 尽量保证图标整体风格简洁,使用大块的渐变背景突出天气特点。在正式开干前,我就仿照小米天气做出了晴、阴、多云这三个图标,方便之后的设计和开发。格式依然采用了 svg,控制组件整体的体积,保证加载速度。

image

开发过程中受到室友启发,尝试为天气图标增加了一些动画,不过有些喧宾夺主,最后不了了之。

3. normal 样式和 detail 样式?#

开发前我其实仅仅计划做出两种样式(即 small 和 medium)。做 normal 样式的主要原因,是开发过程中我发现:当 medium 样式被置于一个宽度过大的元素上方时,会显得内容空洞,不够美观。于是便在 medium 样式的基础上,加长了宽度,增加了空气质量、防晒建议等数据展示。而 detail 样式,纯粹是因为我对小米天气的趋势预报爱得深沉,想要在 DW 中复刻一个出来。

Ⅱ. 开发阶段#

1. 不借助构建工具如何优雅地使用 Web Components?#

在写天气卡片前,我只使用过一次 Web Components,那是在原神玩家信息查询中,当时是因为有很多重复的要素(角色信息),所以想尝试用这个新鲜玩意封装一下。得到的教训就是:如果不用构建工具,又想要较为优雅地开发,template 标签是必不可少的,否则维护代码简直要了我的老命。

2. 如何优雅地显示图标?#

天气图标会在卡片中大量复用(尤其是 detail 样式),如果没有一个比较简洁的调用方式,维护起来会很困难。并且在开发时图标仅设计了 3 个,需要顾及开发后期如何便捷地对图标增删改,尽量降低图标检索和主体代码间的耦合度。在前端中,一般有下面几种图标引入方式:

① 使用 @font-face 引入图标字体文件#

大型图标字体一般都采用这种方式,如 Font Awesome 和 Material Icons。优点是操作直观,能够使用 font-size 或者 color 直接修改图标展现形式,而且得益于浏览器对colr的支持,能够使用彩色图标字体。不过缺点也很明显:维护较为困难,尤其是涉及到渐变填充,目前还没有什么字体制作软件能够较为优雅地完成这个任务。并且某些手机自定义字体的 hook 逻辑可能导致这种方法引入的图标字体无法生效。

不过,在 DW 中也有一部分图标采用了这种方式,那便是风向的图标,图标单色且数量固定不需要频繁修改 (8 个方位),非常适合使用这种方式。

image

② 使用 svg 的 symbol#

这也是很常用的一种图标引用方式,兼容性极好。维护相对方便,能够支持一些动画。AI 能够直接导出图标为 symbol 标签,而且有许多构建工具也能够为此提供支持,基本没有缺点。

然而 DW 的天气图标并没有采用上述的两种方式。我对图标部分使用 Web Components 做了封装,已经是类似 symbol 的作用,因此再使用 symbol 便显得有些多此一举。

封装后的天气图标调用就方便多了,可以直接使用 <dw-icon type="sunny"></dw-icon> 这样的代码来调用特定的图标,下面是一个示例。

之后打算使用 gulp,这种方式也能够为开发提供便利。

3. 自适应怎么做?#

在天气组件的开发过程中,我才发现还原设计稿其实是这其中最简单的一件事。我需要保证卡片中的所有元素都能有条不紊地展现出来,我原本想要固定每一种样式的卡片宽度,这样能够确保卡片的布局总是完美的,但是会使天气卡片的泛用性大打折扣,其他使用 DW 的人并不会专门为了一个小组件而修改自己的布局方案,同时固定宽度意味着在移动设备上,天气卡片的体验会很糟糕。但是自适应,又该怎么做呢?

最常用的自适应方法是写媒体查询,但是我不能使用媒体查询,其他开发者在哪儿插入卡片、怎么插入卡片、卡片的父级元素是什么状态我都无从得知,我不能仅通过屏幕尺寸判断出天气卡片目前的状态。

我也不能通过判断卡片宽度就隐藏或显示某些元素,因为之后的版本 DW 会将数据展示的部分模块化,允许其他开发者自定义展示哪些数据,擅自修改展示的元素可能导致其他开发者的配置没法如预期那样展示出来。

既然谈到了判断父容器尺寸,不如来谈谈实现方式。一般来说,我常用的方法是在父容器中嵌入一个 iframe,通过 iframe 的尺寸变化监听容器尺寸变化,或许未来也可以试试 css 容器查询(Container Queries),能够提供很大便利,不过目前这个特性还处在 pr 阶段…兼容性列表

我一度想要固定卡片宽度,事实上直到我将 DW 的布局和逻辑基本全部写完后,我依然没有找到很好的解决方案。

给我灵感的,是 windows 的资源管理器:

image

天气卡片的主体元素固定在左侧不动,右侧的数据展示根据卡片宽度显示滚动条,实现也非常简单,因为我使用的弹性布局,只要在原来的数据展示区域外边包装一层带有 flex-grow: 1; 样式的容器就好了。

4. detail 样式的数据展示#

这一部分也很复杂,因为社团面试任务中有提到图表展示,当时是想复刻一个小米天气的 15 天趋势预报试试水,如果成了的话之后还可以拓展到小时预报之类的图表展示。图表部分是使用 svg 实现的,为了让暗黑模式的样式操作能够便利,所以使用了 svg 而不是 canvas。绘图直接用的浏览器原生 js 实现,只需要绘制一个折线图,chartjs 显然有些大材小用,比较臃肿。原先设计稿中采用的展示方式很难优雅地在中间位置插入图表,所以后来将早上数据、图表、晚上数据全部分了开来,因为列宽是一致的,所以也不用担心错位的问题。

image

image

接着就是绘制图表了,首先统一计算出折点的 X 坐标,接着按照温度确定出每个折点的 Y 坐标,折点用的是 svg 的 circle 元素,折线部分直接用 path 搞定了。

path 的 d 参数语法逻辑其实和 canvas 绘制的逻辑是相类似的,首先使用 M (MoveTo) 指令将起点移动到第一个点的位置,接着只需要使用 L (LineTo) 指令绘制之剩下折线便完成了。

4. 暗黑模式#

只需要使用 @media(prefers-color-scheme: dark) 这个媒体查询便能够定义暗黑模式下的卡片样式。

值得一提的是,我使用了css 变量,目前大部分浏览器已经兼容了,能够大幅减少重复代码。

有时候使用者可能不想让媒体查询自作主张修改卡片样式,于是乎我提供了属性 theme 来控制卡片颜色。可以使用theme="light" 或是 theme="dark" 将卡片锁定在明亮模式或暗黑模式。这点小功能我想着完全用 css 来实现,之前 Web Components 用得不多,想着用宿主选择器轻松就能搞定,便想当然地写出了下面的这段 css…

:host {
    // 默认样式
}

:host[theme="dark"] {
    // 暗黑模式样式
}

然鹅… 翻车了,样式并不会生效,翻遍了 MDN 后,我找到了这个选择器()~(面向 MDN 编程)~,所以正确的写法应该是这样(所以有哪个翻过 blink 源码的小伙伴能告诉我为什么要这样设计么…):

:host {
    // 默认样式
}

:host([theme="dark"]) {
    // 暗黑模式样式
}

Ⅲ. 总结#

这次写 DW,让我学到了许多,之前写前端很少会自己去做图表生成,经常是引用个 chartjs 或是 echarts 了事。对 Web Components 也有了较之前更为全面的了解,同时也熟悉了一下 flex 布局的使用~,至少 2 天多的小米天气没白看,我同学看我一天到晚拿着手机刷小米天气以为我疯了~。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。