作者:龍朝忠
京東微信購物首頁(以下簡稱微信首頁)曾經作為微信購物一級入口(目前替換為京喜小程式)一直對效能有著極高的要求,本文將介紹微信首頁的一些優化經驗。
一般來說產品是按以下方式進行迭代的,我認為迴圈的起點應該是「收集使用者反饋」,我們對頁面的優化依據和目標一個重要來源就是使用者的反饋,因此說網頁優化我們先從網頁監控開始聊起。
京東監控系統簡介
京東前端監控涉及的系統主要有兩個:測速系統和智慧監控平臺。
測速系統
網頁將各個關鍵節點的測速資訊(時間戳)上傳給系統,系統收集資訊後對每個節點按省份、時間、網路型別、客戶端型別等多個維度進行統計,並提供視覺化分析結果,可以很方便的監控網頁的載入情況。
智慧監控平臺
網頁按照約定格式上報資訊給系統,系統收集資訊後按照預設的分析模式統計分析結果,若分析結果不符合預期還提供給告警功能。
我們在微信首頁 CSS 載入完成、HTML 載入完成、JS 載入完成、首屏圖片載入完成、第一張圖片載入完成等關鍵節點插入測速點,並根據業務特點對關鍵內容上報智慧監控平臺,如查詢首屏 DOM 節點是否存在上報首屏可用率、檢測重要介面返回資訊上報介面可用率。這樣我們就能對微信首頁的執行健康情況有一個比較全面的瞭解。
微信首頁監控的兩個階段
第一階段:主要關注首屏內容的載入優化( 2014 ~ 2019.5 )
這個階段我們我們關注網頁的載入速度,我們選取首屏圖片載入完成時間作為核心監控點,重點關注 CSS 載入完成時間、HTML 載入完成時間、JS 載入完成時間、第一張圖片載入完成時間。
第一階段我們的目標是首屏圖片載入完成時間控制在 1000ms 以內,其他時間越短越好。為達到這個目的,我們採取了一下措施。
1、首屏直出
首屏直出,也就是服務端渲染( SSR ),微信首頁使用的是一個高效的 C++ 模板- CS 模板生成微信首頁首屏內容。
以上是服務端渲染( SSR )和客戶端渲染( CSR )在瀏覽器中的呈現區別,根據我們測試系統檢測採用首屏 SSR 後首屏圖片載入完成時間減少了 1200ms 左右,而且體驗更好了。
2、關鍵渲染路徑優化
關鍵渲染路徑( Critical Render Path )簡稱 CRP ,是指一系列在首屏渲染中必須發生事件,優化關鍵渲染路徑就是優先顯示與當前使用者操作有關的內容。
這是一個不太「精確」的概念,主要是關鍵渲染的規定,這和業務息息相關。關鍵渲染通常來說是指首屏渲染(使用者第一眼可見區域)、頁面的核心內容部分(這個也有點抽象)。
關鍵渲染路徑的三個屬性
- 關鍵資源:可能阻止網頁首次渲染的資源。劃重點:阻止網頁首頁渲染。
- 關鍵路勁長度:獲取所有關鍵資源所需的往返次數或總時間。就是獲取所有關鍵資源要請求多少次。
- 關鍵位元組:實現網頁首次渲染所需的總位元組數,它是所有關鍵資源傳送檔案大小的總和。
阻止網頁首頁渲染的資源
根據瀏覽器工作原理,首先瀏覽器是構建內 DOM 樹和 CSSOM 樹,然後將 DOM 樹和 CSSOM 樹合成「渲染樹」,通過渲染樹計算出佈局資訊然後渲染到螢幕上。
因此從渲染流程上來說,HTML 和 CSS 肯定是阻止網頁首頁渲染的資源,因為沒有它們就不能構建出渲染樹。 JavaScript 因為可能修改 DOM 或 CSSOM ,因此預設情況下瀏覽器在解析到 script 標籤時會停止 DOM 樹的構建,等 JavaScript 執行完再從 script 標籤位置重新開始構建 DOM ,所以說 JavaScript 也是阻止網頁首頁渲染的資源。
根據關鍵渲染路徑理論,我們可以從三個方面去優化網頁:
- 儘量減少網頁首次渲染的資源
- 減少關鍵路徑長度,減少請求次數
- 減少關鍵資源大小
2.1、儘量減少網頁首次渲染的資源——拆分首屏和非首屏
拆分首屏和非首屏目的是劃分出關鍵資源,我們定義除底部 tab 以上的的部分為首屏內容,這部分內容使用者會最先看到,後面的優化措施就是儘量讓首屏內容儘快展示。
對於非首屏內容採取延遲載入的方式處理。JS、CSS 非同步載入 ,圖片資源懶載入(快進入可視區域時載入)。
2.2、減少關鍵路徑長度,減少請求次數
關鍵渲染路徑長度是指獲取關鍵資源網路請求次數
對於這塊的優化,我們採取了一下措施:
- 首屏樣式和 JS 內聯
- 合併 JS 檔案到一個 JS
- 首屏 ICON 圖片內聯處理
- 底部導航圖示合成雪碧圖
2.3、減少關鍵資源大小
對於首屏資源我們按類別分別作了一下優化處理。
對於 HTML,我們使用 html-minifier 工具精簡HTML內容,去除不必要的空格和換行。
對於 JS,我們基於 webpack 對其進行 Treeshaking ,使用 webpack 對 JS 進行 treeshaking 依賴 ES2015(ES6) 模組系統中的靜態結構特性,因此這部分的優化需要對 JS 進行 ES6 改造。
對於 CSS,開發過程中經常出現某次活動的樣式在活動下線後忘記去掉,到最後不敢輕易去掉,造成不少無用樣式存在。打包的時候我們使用 purifyCSS 對這種樣式進行刪除。改工具的實現原理可以開闊為:將 CSS 選擇器名稱切割成一個個單詞,然後在所有可能用到的檔案中查詢這些單詞,若單詞在沒有出現在任何地方說明該 CSS 選擇器對應的樣式沒有用到,可以刪除。
微信首頁由於歷史的積累,存在不少無用樣式,使用 purifyCSS 工具處理後能節省 58KB 的關鍵資源大小。
對於 JSON 檔案 ,首頁內容大都需要運營配置,因此存在大量 JSON 資料,經過長年的積累對效能的消耗已不容忽視,如下面的一個配置的解析就佔用了 200ms。這塊我們一是推動推動運營刪除過期資料,二是推動優化 JSON 資料介面,介面智慧刪除過期資料。
3.圖片的優化
3.1、使用 WEBP 和 DPG 格式代替 PNG 和 JPG。
我們在客戶端檢測當前環境是否支援 WEBP 和 DPG,並提供統一的轉換函式,服務端也提供了相同的功能。
根據我們實驗對比發現:
1、DPG 格式和 WEBP 格式均有明顯的壓縮效果,壓縮比例平均在60%以下;
2、DPG 壓縮比 WEBP 壓縮的效果稍微更好一些;
3、DPG + WEBP 雙壓縮比單種格式壓縮有更明顯的提升,達到 30%。
3.2、圖片無失真壓縮
這塊包含兩方面的措施,一是我們在使用工具釋出微信首頁時,對頁面直接依賴的圖片做無失真壓縮,這是後圖片大都是設計師給的切圖,切圖存在大量無用的資訊,這時候無失真壓縮一半能節省一半的大小。
另一方面是藉助京東圖片服務壓縮圖片,我們需要按圖片服務要求格式訪問圖片即可獲得壓縮處理後的圖片。
3.3 使用MP4代替GIF
根據我們測試對比,絕大情況下 MP4 的大小要比 GIF 小很多
這張圖 GIF 大小為 125KB 轉成 MP4 後變為 86KB ,減少了 31.2% 。
這張圖 GIF 大小為 200KB 轉成 MP4 後變為 90KB ,減少了 55% 。
4.資源預載入
4.1、Preload
Preload 是一個新的控制特定資源如何被載入的新的 Web 標準,這是已經在 2016 年 1 月廢棄的 subresource prefetch 的升級版。一般來說,最好使用 preload 來載入你最重要的資源,比如影像,CSS ,JavaScript 和字型檔案。這不要與瀏覽器預載入混淆,瀏覽器預載入只預先載入在HTML中宣告的資源。Preload 指令事實上克服了這個限制並且允許預載入在 CSS 和 JavaScript 中定義的資源,並允許決定何時應用每個資源。
我們使用 Preload 載入微信首頁頭部 banner 第一張圖和頭部氛圍圖,這樣能讓使用者更早看到完整的首屏內容。
4.2 Preconnect
Preconnect 是 HTTP 請求正式發給伺服器前預先執行一些操作,這包括 DNS 解析,TLS 協商,TCP 握手,這消除了往返延遲併為使用者節省了時間。我們對頁面中常用域名做了 Preconnect 。
4.3 DNS prefetch/ Link-prefetch/Prerending
DNS prefetching 允許瀏覽器在使用者瀏覽頁面時在後臺執行 DNS 的解析。如此一來,DNS 的解析在使用者點選一個連結時已經完成,所以可以減少延遲。可以在一個 link 標籤的屬性中新增 rel="dns-prefetch" 來對指定的 URL 進行 DNS prefetching。
Link prefetching 假設使用者將請求指定的 url,瀏覽器在空閒的時候獲取資源並將他們儲存在快取中。
Prerendering 和 prefetching 非常相似,它們都優化了可能導航到的下一頁上的資源的載入,區別是 prerendering 在後臺渲染了整個頁面,整個頁面所有的資源。
對當前頁面效能無提升,但是若瀏覽器支援,對跳轉到的下一頁意義很大。
第二階段:以 RAIL 模型為基礎的多維度優化( 2019.5 ~ now )
一直以來,我們都用「頁面首屏圖片載入時間」這個指標來作為優化我們效能的關鍵 KPI。但是此指標對於「頁面白屏時間很長」、「進度條載入慢」、「搜尋框、輪播 banner、底部導航三個模組出來比較慢」幾個體驗問題,是無法衡量的。即使我們把「頁面首屏圖片載入時間」這個資料優化的很小,也並不意味著頁面的效能和體驗很好。這說明拿這個來衡量頁面效能遠遠不夠,我們需要更多維度的效能指標來衡量頁面的效能。另外,「頁面首屏圖片載入時間」是一個複合動作後的資料結果,包含了 css/js 載入和解析,以及圖片的載入和渲染等綜合情況,並不能很好的指導頁面做效能優化。再者,這個指標並不是一個標準指標,跟開發同學具體的埋點很有關係,有些頁面還很不好埋點(比如有些內容新人才可見,怎麼算首屏)。綜上來說,我們需要有更多維度的、更標準的效能指標來描述頁面的效能,並指導頁面做效能優化。
我們採用 Google 的 RAIL 模型,此模型關注 Web 應用生命週期的四個方面:響應( Response ,響應時間不超過 100ms ),動畫( Animation,10ms 完成一幀),空閒( Idle,空閒時間越多越好),載入( Load,1000ms 內完成載入),並提出以使用者為中心的效能指標。
RAIL 模型的願景
- 網頁效能優化要以使用者為中心;最終目標不是讓您的網站在任何特定裝置上都能執行很快,而是使使用者滿意。
- 網頁應該立即響應使用者;在 100 毫秒以內確認使用者輸入。
- 網頁應該在設定動畫或滾動時,在 10 毫秒以內生成幀。
- 網頁應該最大程度增加主執行緒的空閒時間。
- 網頁應該持續吸引使用者;在 1000 毫秒以內呈現互動內容。
RAIL 模型對應的四個評估維度
- Response:頁面響應使用者的操作應該 100ms 內
- Animation:對於頁面中的動畫,應該再 10ms 內生成一幀
- Idle:要實現小於 100 毫秒的響應,應用必須在每 50 毫秒內將控制返回給主執行緒
- Load:要求您的網頁在 1000ms 內呈現關鍵路徑內容給使用者
新效能模型下監控側重點
當我們採用以使用者為中心的效能模型時,我們肯定也需要採用以使用者為中心的效能指標。
1、首次繪製時間(FP): FP 標記瀏覽器渲染任何在視覺上不同於導航前螢幕內容之內容的時間點
2、首次內容繪製時間(FCP): FCP 標記的是瀏覽器渲染來自 DOM 第一位內容的時間點,該內容可能是文字、影像、SVG 甚至 canvas 元素
4、首次有效繪製(FMP):這是一個「模糊」的概念,是指頁面的主要元素開始繪製的時間
5、可互動時間(TTI): 用於標記應用已進行視覺渲染並能可靠響應使用者輸入的時間點。
6、Long Tasks 監控:根據實測,使用支援最好的 Chrome 實驗,獲得的監控結果也不太有用,因此 Long Task 監控展示作罷。
第二階段的效能優化
第二階段的效能優化基於第一階段的基礎上,為了能達到 RAIL 模型要求,我們進一步做了一下事情。
1、進一步深化關鍵渲染路徑的優化
我們站在使用者的角度,結合京東微信購物首頁流量轉化情況,分析認為首頁除了首屏廣告 banner,搜尋框和底部導航作為使用者使用頻率最高的幾個模組應該提前渲染。並以首屏廣告 banner 作為首次有效繪製。
對於搜尋框,之前需要載入 3 個 JS 請求和 1 個 CSS 請求才能渲染出來,致使搜尋框的渲染嚴重滯後。我們把之前通過 JS 渲染的 DOM 直接以頁面片形式引入,並將 CSS 樣式內聯,這樣搜尋框能在首屏載入時就顯示出來,然後我們將 3 個 JS 檔案合併成一個,這樣就加快了搜尋框的初始化。
對於底部導航依賴了一個獨立的 CSS 檔案,而且在很靠下的位置,我們把底部導航的程式碼提前到搜尋框的下面,並將樣式內聯。
2、動畫優化
動畫是造成頁面卡頓的重要元凶之一,尤其是是用 setInterval 實現的動畫,容易造成丟幀現象。因此我們用 requestAnimationFarme 代替 setInterval ,解決了部分機型動畫卡頓問題 。
3、滾動優化
當直接監聽頁面滾動時間時,由於滾動事件觸發頻率很高,即使一個簡單的 handler 函式也會造成大量的開銷。因此我們對滾動事件做了節流,只允許一個函式在 X 毫秒內執行一次,只有當上一次函式執行後過了你規定的時間間隔,才能進行下一次該函式的呼叫。
4、圖片懶載入優化
為了實現圖片 DOM 渲染時不載入,等到快進入可視區域時載入,我們需要不聽的觀察圖片是否進入了可視區域。之前我們做法是開啟定時任務,無限迴圈查詢 img 標籤是否在可視區,很容易生成 Long Task,造成頁面響應遲鈍。
使用最新的 IntersectionObserver 介面代替定時任務,將監控img是否可見的任務交給瀏覽器,能顯著提高效率。
結束語
前端技術日新月異,網頁的優化也是如此。如經典的雅虎軍規,許多規則到現在仍然具有重要的指導意義,我們在日常的開發中也仍在嚴格遵守著,但是有一些則該謹慎看待。如進入 HTTP2 時代後,資源的合併就失去了意義,甚至從快取角度來看會起相反的作用。我們在微信首頁所做的這些優化措施可能對你的頁面並不適用,但希望能給你一些啟迪。
如果你覺得這篇內容對你有價值,請點贊,並關注我們的官網和我們的微信公眾號(WecTeam),每週都有優質文章推送: