我給網站做了一場性能手術

我給網站做了一場性能手術

體檢

市面上的體檢套餐有很多種,但其實都是換湯不換藥. 那葯(標準)是什麼呢? 我們會在下面說明. 這裡我選擇了 Google 提供的檢測工具燈塔(LightHouse)進行性能體檢.

體檢標準

為什麼我說大多體檢套餐都是換湯不換藥呢? 我們先從燈塔的計分規則說起:

從上面中我們可以看到燈塔v6/v7版是通過幾種性能指標及不同權重來進行計分的. 這幾種指標主要是根據PerformanceTimingPerformanceEntry API標準進行定義. 市面上大多體檢套餐也是基於這些指標定製的. 接下來我們來瞭解下這些指標的含義吧.

FCP (First Contentful Paint)

成像第一個元素(文字、圖片、canvas...) 的時間點

SI (Speed Index)

首屏展現時間

LCP (Largest Contentful Paint)

渲染可視區域內最大內容元素的時間點

TTI (Time to Interactive)

頁面資源載入成功並能回應使用者交互的時間點

TBT (Total Blocking Time)

FCP到TTI之間,主線程被long task(超過50ms)阻塞的時間之和

CLS (Cumulative Layout Shift)

累計佈局偏移值

FID (First Input Delay)

使用者第一次在頁面進行交互(點擊連結、按鈕、自定義js事件),到瀏覽器實際開始處理這個事件的時間

Core Web Vitals

談到用戶體驗與性能指標,順便提下Core Web Vitals. 2020年5月,Google針對網站使用體驗推出了一套核心指標標準(Core Web Vitals).由三項指標構成:

為什麼不是別的指標呢 ? 因為這套標準主要從以下三個維度進行評估:

  • [載入情況] : LCP
  • [互動性] : FID
  • [視覺穩定性] : CLS

如何檢視Core Web Vitals 指標 ?

開發者可利用以下幾種工具對Core Web Vitals進行監測:

由於FID需要一個真實使用者的交互,所以無法用實驗數據測試. 為了能在實驗資料下測試FID,通常會用TBT (Total Blocking Time).雖然他們測量的內容不同,但改善TBT通常也能改善FID.

體檢結果

不檢不知道,一檢嚇一跳.6個重要器官涼了一半... 是時候對它動個手術了!

指標評分

### 改善建議

## 手術

手術方案

既然是性能手術,方案就主要以性能指標作為維度,主要分為以下幾個點:

  • 視覺穩定性 (Cumulative Layout Shift)
  • 載入情況 (Largest Contentful Paint)
  • TTI (Time to Interactive)
  • TBT (Total Blocking Time)
  • FCP (First Contentful Paint)

手術過程

視覺穩定性 (Cumulative Layout Shift)

  • 優化未設置尺寸的圖片元素

改善建議裡提到了一項優先順序很高的優化就是為圖片元素設置顯式的寬度和高度,從而減少佈局偏移和改善CLS.

```html
<imgsrc=hello.pngwidth=640height=320alt=Hello World/>


- 自定義字體檔載入期間保持可見狀態

> 改善建議裡提到使用CSS font-display屬性確保自訂字型檔案在載入期間可見.

![](https://hypergrowths.com/wp-content/uploads/2021/05/v2-c7c1c74304feeb53fb67cdded44de22e_r.png "v2-c7c1c74304feeb53fb67cdded44de22e_r")![](https://hypergrowths.com/wp-content/uploads/2021/05/v2-c7c1c74304feeb53fb67cdded44de22e_r.png "v2-c7c1c74304feeb53fb67cdded44de22e_r")> 這是因為網站下載自定義字體檔需要一段時間,而不同瀏覽器此時的行為是不同的. 一些瀏覽器在載入自訂字體時會隱藏文字,這種稱之為**FOIT(Flash Of Invisible Text**).而一些瀏覽器會顯示降級字體,這種情況稱之為**FOUT(Flash Of Unstyled Tex)**.這兩種行為會導致字體閃爍問題,影響視覺穩定性 (CLS).
>  我的處理方法是直接設置font-display:swap; 這個屬性能確保字型在載入時間可見. 雖然還是會引發FOUT,但是相比FOIT,FOUT對視覺穩定性的影響會小一些.
>  更好的方案應該是預載入(preload)字型檔案. 讓字體下載有更高概率趕在FCP之前,從而避免FOIT/FOUT.

```css
@font-face{font-family:'Hello-World';src:url('../font/Hello-World.otf')format('OpenType');/* swap:如果设定的字体还未可用,浏览器将首先使用备用字体显示,当设定的字体加载完成后替换备用字体 */font-display:swap;}
  • 避免頁面布局發生偏移

![](https://hypergrowths.com/wp-content/uploads/2021/05/v2-2611316bb1aac65e6912be9495fe20e6_r.png "v2-2611316bb1aac65e6912be9495fe20e6_r")![](https://hypergrowths.com/wp-content/uploads/2021/05/v2-2611316bb1aac65e6912be9495fe20e6_r.png "v2-2611316bb1aac65e6912be9495fe20e6_r")> 我們產品中有一個頂部動態插入的元素,這個元素會導致網站整體佈局下移. 從而造成了較大的佈局偏移. 跟產品及ui py交易後,我們友好地對這個元素進行了調整. 將此元素脫離文件串流,採用固定定位的方式進行展示. 從而解決該問題.

  • 避免非合成動畫

![](https://hypergrowths.com/wp-content/uploads/2021/05/v2-76f6a2e8f5358683b927d4bbcc7f6c0b_r.jpg "v2-76f6a2e8f5358683b927d4bbcc7f6c0b_r")![](https://hypergrowths.com/wp-content/uploads/2021/05/v2-76f6a2e8f5358683b927d4bbcc7f6c0b_r.jpg "v2-76f6a2e8f5358683b927d4bbcc7f6c0b_r")> 改善建議中提到應避免使用非合成動畫,非合成動畫會使得頁面變得混亂並增加CLS.關於這個優化建議我覺得應該具體場景具體分析,不應該因噎廢食.畢竟目前能被composited的css屬性只有transform & opacity.當然這也在提醒我們平時在做CSS動畫時應注意優化 (比如常見的使用transform替代top).

載入情況 (Largest Contentful Paint)

  • 替換最大內容繪製元素

在改善建議中,我發現網站的最大內容繪製元素是谷歌地圖中的一個圖塊元素. 這也難怪LCP指標的數據表現不理想了,原因: 鏈路過長 - 下載谷歌地圖Js sdk => 初始化谷歌地圖=> 繪製 .

於是,我決定對最大內容繪製元素進行修改,從而提升LCP時間. 我喵了一眼Largest Contentful Paint API 關於該元素類型的定義,將目標鎖定到了一個loading元素 (繪製成本低: 預設渲染,不依賴任何條件和判斷).經過我對該元素的尺寸動了手腳後(變大),該元素成功上位.

TBT (Total Blocking Time) / TTI (Time to Interactive)

  • 非同步載入谷歌地圖Js sdk
    原先載入 Google 地圖 Js sdk 是透過動態加入 script 標籤同步載入的. 這樣做的缺點其實是很明顯的:
  • Google Maps Js sdk 載入時機太晚,影響TTI表現和用戶體驗.
  • js引擎占據主線程進行相關js執行. 我的處理方案就是對谷歌地圖Js sdk進行異步載入. 這裡需要注意的是script async/defer的區別,我使用的是defer進行非同步載入(async載入完畢後會立即執行,阻塞主線程,影響DOM解析).
<scriptsrc=//maps.googleapis.com/maps/api/jstype=text/javascriptdefer>script>
  • 優化構建bundle體積

檢視基於webpack-bundle-analyzer生成的體積分析報告我發現有兩個可優化的大產物:

  • lottie動畫庫

    網站只有一個動畫效果的實現用到該庫,跟產品、ui又一頓py后,我們決定犧牲一點視覺效果.移除lottie library,改用CSS3實現.

  • ant-design-vue中內置的momentjs依賴

    momentjs的語言包(locale)體積非常大,而網站並無國際化需求. 所以這裡我直接使用webpack IgnorePlugin對語言包進行忽略. 經過優化,bundle體積(gizp前)由原來的1.8MB減小至1.3MB.

FCP (First Contentful Paint)

網站使用的是Vue做的客戶端渲染. 這也意味著FCP過程會有點「漫長」. (初始化Vue實例等一系列工作需要佔用主線程執行Js).這裡我自作聰明地在html檔添加了透明文本占位符,搶佔FCP時間. 這個騷操作我個人認為有點抖機靈,大家可以選擇性無視...

<divid=app><pstyle=color:#fff;>Hello Worldp>div>

其他

除了針對上面幾個指標維度進行優化外,我還做了幾點優化,這裡簡單提一下:

  • 優化DOM嵌套層級及數量
  • 減少不必要的介面請求
  • 使用translate替換top做位移/動畫

手術結果

說了那麼多「廢話」,那手術結果究竟如何呢 ? 是華麗變身還是「反向一Q日神仙」呢 ? 直接上圖:

通過上圖我們可以看到各項指標及評分都有質的飛躍,雖然我不要臉地截了個最高分 (LightHouse每次評分會有波動,實際效果是由原來的50-70分漲到了70-90分) !!!

Web Performance Metrics 與 Core Web Vitals 簡介

谷歌發佈2021年版網頁排名演算法 用戶體驗成為重要考核