戴兜

戴兜的小屋

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 天多的小米天氣沒白看,我同學看我一天到晚拿著手機刷小米天氣以為我瘋了~。

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