企鵝輔導 H5 頁面在長期迭代過程當中,逐漸累積了一些性能問題,致使頁面加載、渲染速度變慢。爲了提高用戶體驗,近期針對頁面加載速度,渲染速度作了專項優化,本文是對這次優化的實踐總結。分析過程比較細緻,但願能給性能分析經驗欠缺的同窗一些幫助。javascript
H5 項目是企鵝輔導的核心項目,已迭代四年多,包括了課程詳情頁/老師詳情頁/報名頁/支付頁面等頁面,構建產物用於企鵝輔導 APP/H5(微信/QQ/瀏覽器),迭代過程當中了也累積了一些性能問題致使頁面加載、渲染速度變慢, 爲了提高用戶體驗,近期啓動了「H5 性能優化」項目,針對頁面加載速度,渲染速度作了專項優化,下面是對本次優化的總結,包括如下幾部份內容。css
企鵝輔導 H5 採用的性能指標包括:html
1.頁面加載時間:頁面以多快的速度加載和渲染元素到頁面上。前端
2.加載後響應時間:頁面加載和執行js代碼後多久能響應用戶交互。java
3.視覺穩定性:頁面元素是否會以用戶不指望的方式移動,並干擾用戶的交互。node
項目使用了 IMLOG 進行數據上報,ELK 體系進行現網數據監控,Grafana 配置視圖,觀察現網狀況。react
根據指標的數據分佈,能及時發現頁面數據異常採起措施。webpack
現網頁面狀況:ios
能夠看到進度條在頁面已經展現後還在持續 loading,加載時間長達十幾秒,比較影響了用戶體驗。nginx
根據 Google 開發文檔 對瀏覽器架構的解釋:
當導航提交完成後,渲染進程開始着手加載資源以及渲染頁面。一旦渲染進程「完成」(finished)渲染,它會經過IPC告知瀏覽器進程(注意這發生在頁面上全部幀(frames)的 onload 事件都已經被觸發了並且對應的處理函數已經執行完成了的時候),而後UI線程就會中止導航欄上旋轉的圈圈
咱們能夠知道,進度條的加載時長和 onload 時間密切相關,要想進度條儘快結束就要 減小 onload時長。
根據現狀,使用ChromeDevTool做爲基礎的性能分析工具,觀察頁面性能狀況
Network:觀察網絡資源加載耗時及順序
Performace:觀察頁面渲染表現及JS執行狀況
Lighthouse:對網站進行總體評分,找出可優化項
下面以企鵝輔導課程詳情頁爲案例進行分析,找出潛在的優化項
(注意使用Chrome 隱身窗口並禁用插件,移除其餘加載項對頁面的影響)
一般進行網絡分析須要禁用緩存、啓用網絡限速(4g/3g) 模擬移動端弱網狀況下的加載狀況,由於wifi網絡可能會抹平性能差距。
能夠看到DOMContentLoaded的時間在 6.03s ,但onload的時間卻在 20.92s
先觀察 DOMContentLoaded 階段,發現最長請求路徑在 vendor.js ,JS大小爲170kB,花費時間爲 4.32s
繼續觀察 DOMContentLoaded 到 onload 的這段時間
能夠發現onload事件被大量媒體資源阻塞了,關於 onload 事件的影響因素,能夠參考這篇文章
結論是 瀏覽器認爲資源徹底加載完成(HTML解析的資源 和 動態加載的資源)纔會觸發 onload
結合上圖 能夠發現加載了圖片、視頻、iFrame等資源,阻塞了 onload 事件的觸發
Network 總結
使用Performance模擬移動端注意手機處理器能力比PC差,因此通常將 CPU 設置爲 4x slowdown 或 6x slowdown 進行模擬
觀察幾個核心的數據
能夠看到 LCP、DCL和 Onload Event 時間較長,且出現了屢次 Layout Shift。
要 LCP 儘可能早觸發,須要減小頁面大塊元素的渲染時間,觀察 Frames 或ScreenShots 的截圖,關注頁面的元素渲染狀況。
能夠經過在 Experience 行點擊Layout Shift ,在 Summary 面板找到具體的偏移內容。
能夠看到頁面有大量的Long Tasks須要進行優化,其中couse.js(頁面代碼)的解析執行時間長達800ms。
處理Long Tasks,能夠在開發環境進行錄製,這樣在 Main Timeline 能看到具體的代碼執行文件和消耗時長。
Performance 總結
使用ChromeDevTool 內置 lighthouse 對頁面進行跑分
分數較低,能夠看到 Metrics 給出了核心的數據指標,這邊顯示的是 TTI SI TBT 不合格,LCP 須要提高,FCP 和 CLS 達到了良好的標準,能夠查看分數計算標準
同時 lighthouse 會提供一些 優化建議,在 Oppotunities 和 Diagnostics 項,能看到具體的操做指南,如 圖片大小、移除無用JS等,能夠根據指南進行項目的優化。
lighthouse 的評份內容是根據項目總體加載項目進行打分的,審查出的問題一樣包含Network、Performance的內容,因此也能夠看做是對 Network、Performance問題的優化建議。
Lighthouse 總結
剛纔是對線上網頁就行初步的問題分析,要實際進行優化和觀察,須要進行環境的模擬,讓優化效果能更真實在測試環境中體現。
代理使用:whistle、charles、fiddler等
本地環境、測試環境模擬:nginx、nohost、stke等
數據上報:IMLOG、TAM、RUM等
前端代碼打包分析:webpack-bundle-analyzer 、rollup-plugin-visualizer等
分析問題時使用本地代碼,本地模擬線上環境驗證優化效果,最後再部署到測試環境驗證,提升開發效率。
Network 中對頁面中加載的資源進行分類
第一部分是影響 DOM解析的JS資源,能夠看到這裏分類爲 關鍵JS和非關鍵JS,是根據是否參與首面渲染劃分的
這裏的非關鍵JS咱們能夠考慮延遲異步加載,關鍵JS進行拆分優化處理
JS 文件數量8個,整體積 460.8kB,最大文件 170KB
vendor.js 170kB(gzipd) 是全部頁面都會加載的公共文件,打包規則是 miniChunks: 3,引用超過3次的模塊將被打進這個js
分析vendor.js的具體構成(上圖)
以string-strip-html.umd.js 爲例 大小爲34.7KB,佔了 vendor.js的 20%體積,但只有一個頁面屢次使用到了這個包,觸發了miniChunks的規則,被打進了vendor.js。
同理對vendor.js的其餘模塊進行分析,iosSelect.js、howler.js、weixin-js-sdk等模塊都只有三、4個頁面/組件依賴,但也一樣打進了 vendor.js。
由上面的分析,咱們能夠得出結論:不能簡單的依靠miniChunks規則對頁面依賴模塊進行抽離打包,要根據具體狀況拆分公共依賴。
修改後的vendor根據業務具體的需求,提取不一樣頁面和組件都有的共同依賴(imutils/imlog/qqapi)
vendor: {
test({ resource }) {
return /[\\/]node_modules[\\/](@tencent\/imutils|imlog\/)|qqapi/.test(resource);
},
name: 'vendor',
priority: 50,
minChunks: 1,
reuseExistingChunk: true,
},
複製代碼
而其餘未指定的公共依賴,新增一個common.js,將閾值調高到20或更高(當前頁面數76),讓公共依賴成爲大多數頁面的依賴,提升依賴緩存利用率,調整完後,vendor.js 的大小減小到 30KB,common.js 大小爲42KB
兩個文件加起來大小爲 72KB,相對於優化前體積減小了 60%(100KB)
course.js 101kB (gzipd) 這個文件是頁面業務代碼的文件
觀察上圖,基本都是業務代碼,除了一個巨大的** component Icon,佔了 25k**,頁面文件1/4的體積,但在代碼中使用到的 Icon 總共才8個
分析代碼,能夠看到這裏使用require加載svg,Webpack將require文件夾內的內容一併打包,致使頁面 Icon 組件冗餘
如何解決這類問題實現按需加載?
按需加載的內容應該爲獨立的組件,咱們將以前的單一入口的 ICON 組件(動態dangerouslySetInnerHTML)改爲單文件組件模式直接引入使用圖標。
但實際開發中這樣會有些麻煩,通常須要統一的 import 路徑,指定須要的圖標再加載,參考 babel-plugin-import,咱們能夠配置 babel 的依賴加載路徑調整 Icon 的引入方式,這樣就實現了圖標的按需加載。
按需加載後,從新編譯,查看打包帶來的收益,頁面的 Icons 組件 stat size 由 74KB 降到了 20KB,體積減小了 70%
觀察頁面,能夠看到」課程大綱「、」課程詳情「、」購課須知「這三個模塊並不在頁面的首屏渲染內容裏,
咱們能夠考慮對頁面這幾部分組件進行拆分再延遲加載,減小業務代碼JS大小和執行時長
拆分的方式不少,可使用react-loadable、@loadable/component 等庫實現,也可使用React 官方提供的React.lazy
拆分後的代碼
代碼拆分會致使組件會有渲染的延遲,因此在項目中使用應該綜合用戶體驗和性能再作決定,經過拆分也能使部分資源延後加載優化加載時間。
項目中使用了 TreeShaking的優化,用時候要注意 sideEffects 的使用場景,以避免打包產物和開發不一致。
通過上述優化步驟,總體打包內容:
JS 文件數量6個,整體積 308KB,最大文件體積 109KB
關鍵 JS 優化數據對比:
文件整體積 | 最大文件體積 | |
---|---|---|
優化前 | 460.8 kb | 170 kb |
優化後 | 308 kb | 109 kb |
優化效果 | 整體積減小 50% | 最大文件體積減小 56% |
頁面中包含了一些上報相關的 JS 如 sentry,beacon(燈塔 SDK)等,對於這類資源,若是在弱網狀況,可能會成爲影響 DOM 解析的因素
爲了減小這類非關鍵JS的影響,能夠在頁面完成加載後再加載非關鍵JS,如sentry官方也提供了延遲加載的方案
在項目中還發現了一部分非關鍵JS,如驗證碼組件,爲了在下一個頁面中能利用緩存儘快加載,因此在上一個頁面提早加載一次生成緩存
若是不訪問下一個頁面,能夠認爲這是一次無效加載,這類的提早緩存方案反而會影響到頁面性能。
針對這裏資源,咱們可使用 Resource Hints,針對資源作 Prefetch 處理
檢測瀏覽器是否支持 prefech,支持的狀況下咱們能夠建立 Prefetch 連接,不支持就使用舊邏輯直接加載,這樣能更大程度保證頁面性能,爲下一個頁面提供提早加載的支持。
const isPrefetchSupported = () => {
const link = document.createElement('link');
const { relList } = link;
if (!relList || !relList.supports) {
return false;
}
return relList.supports('prefetch');
};
const prefetch = () => {
const isPrefetchSupport = isPrefetchSupported();
if (isPrefetchSupport) {
const link = document.createElement('link');
link.rel = 'prefetch';
link.as = type;
link.href = url;
document.head.appendChild(link);
} else if (type === 'script') {
// load script
}
};
複製代碼
優化效果:非關鍵JS不影響頁面加載
能夠觀察到onload被大量的圖片資源和視頻資源阻塞了,可是頁面上並無展現對應的圖片或視頻,這部份內容應該進行懶加載處理。
處理方式主要是要控制好圖片懶加載的邏輯(如 onload 後再加載),能夠藉助各種 lazyload 的庫去實現。 H5項目用的是位置檢測(getBoundingClientRect )圖片到達頁面可視區域再展現。
但要注意懶加載不能阻塞業務的正常展現,應該作好超時處理、重試等兜底措施
課程詳情頁 每張詳情圖的寬爲 1715px,以6s爲基準(375px)已是 4x圖了,大圖片在弱網狀況下會影響頁面加載和渲染速度
使用CDN 圖牀尺寸大小壓縮功能,根據不一樣的設備渲染不一樣大小的圖片調整圖片格式,根據網絡狀況,渲染不一樣清晰度的圖
能夠看到在弱網(移動3G網絡)的狀況下,同一張圖片不一樣尺寸加載速度最高和最低相差接近6倍,給用戶的體驗大相徑庭
CDN配合業務具體實現:使用 img 標籤 srcset/sizes 屬性和 picutre 標籤實現響應式圖片,具體可參考文檔
使用URL動態拼接方式構造url請求,根據機型寬度和網絡狀況,判斷當前圖片寬度倍數進行調整(如iphone 1x,ipad 2x,弱網0.5x)
優化效果:移動端 正常網絡狀況下圖片體積減少 220%、弱網狀況下圖片體積減少 13倍
注意實際業務中須要視覺同窗參與,評估圖片的清晰度是否符合視覺標準,避免反向優化!
iframe
加載 iframe 有可能會對頁面的加載產生嚴重的影響,在 onload 以前加載會阻塞 onload 事件觸發,從而阻塞 loading,可是還存在另外一個問題
以下圖所示,頁面在已經 onload 的狀況下觸發 iframe 的加載,進度條仍然在不停的轉動,直到 iframe 的內容加載完成。
能夠將iframe的時機放在 onload 以後,並使用setTimeout觸發異步加載iframe,可避免iframe帶來的loading影響
數據上報
項目中使用 image 的數據上報請求,在正常網絡狀況下可能感覺不到對頁面性能的影響
但在一些特殊狀況,如其中一個圖片請求的耗時特別長就會阻塞頁面 onload 事件的觸發,延長 loading 時間
解決上報對性能的影響問題有如下方案
H5項目採用了延遲合併上報的方案,業務可根據實際須要進行選擇
優化效果:所有數據上報在onload後處理,避免對性能產生影響。
字體優化
項目中可能會包含不少視覺指定渲染的字體,當字體文件比較大的時候,也會影響到頁面的加載和渲染,可使用 fontmin 將字體資源進行壓縮,生成精簡版的字體文件、
優化前:20kB => 優化後:14kB
目前咱們在STKE部署了直出服務,經過監控發現直出平均耗時在 300+ms
TTFB時間在 100 ~ 200 之間波動,影響了直出頁面的渲染
經過日誌打點、查看 Nginx Accesslog 日誌、網關監控耗時,得出如下數據(如圖)
登錄 NGW 所在機器,ping STKE機器,有如下數據
平均時延在 32ms,tcp 三次握手+返回數據(最後一次 ack 時發送數據)= 2個 rtt,約 64ms,和日誌記錄的數據一致
查看 NGW 機器所在區域爲天津,STKE 機器所在區域爲南京,能夠初步判斷是由機房物理距離致使的網絡時延,以下圖所示
切換NGW到南京機器 ping STKE南京的機器,有如下數據:
同區域機器 ping 的網絡時延只有 0.x毫秒,以下圖所示:
綜合上述分析,直出頁面TTFB時間過長的根本緣由是:NGW 網關部署和 Nginx、STKE 不在同一區域,致使網絡時延的產生
解決方案是讓網關和直出服務機房部署在同一區域,執行了如下操做:
優化前
優化後
優化效果如上圖:
七天網關平均耗時 | |
---|---|
優化前 | 153 ms |
優化後 | 31 ms 優化 80%(120 ms) |
模擬弱網狀況(slow 3g)Performance 錄製頁面渲染狀況,從下圖Screenshot中能夠發現
CSS不會阻塞頁面解析,但會阻塞頁面渲染,若是CSS文件較大或弱網狀況,會影響到頁面渲染時間,影響用戶體驗。
藉助 ChromeDevTool 的 Coverage 工具(More Tools裏面),錄製頁面渲染時CSS的使用率
發現首屏的CSS使用率才15%,能夠考慮對頁面首屏的關鍵CSS進行內聯,讓頁面渲染不被CSS阻塞,再把完整CSS加載進來
實現Critial CSS 的優化能夠考慮使用 critters
優化後效果:
CSS 資源正在下載時,頁面已經能正常渲染顯示了,對比優化前,渲染時間上 提高了 1~2 個 css 文件加載的時間。
觀察頁面的元素變化
優化前(左圖):圖標缺失、背景圖缺失、字體大小改變致使頁面抖動、出現非預期頁面元素致使頁面抖動
優化後:內容相對固定, 頁面元素出現無突兀感
主要優化內容:
優化效果由如下指標量化
首次內容繪製時間FCP(First Contentful Paint):標記瀏覽器渲染來自 DOM 第一位內容的時間點
視窗最大內容渲染時間LCP(Largest Contentful Paint):表明頁面可視區域接近完整渲染
加載進度條時間:瀏覽器 onload 事件觸發時間,觸發後導航欄進度條顯示完成
Chrome 模擬器 4G 無緩存對比(左優化前、右優化後)
首屏最大內容繪製時間 | 進度條加載(onload)時間 | |
---|---|---|
優化前 | 1067 ms | 6.18s |
優化後 | 31 ms 優化 80%(120 ms) | 1.19s 優化 81% |
Lighthouse 跑分對比
優化前
優化後
性能得分 | |
---|---|
優化前 | 平均 40 ~ 50 |
優化後 | 平均 75 ~ 85 提高 47% |
srobot 性能檢測一週數據
srobot 是團隊內的性能檢測工具,使用TRobot指令一鍵建立頁面健康檢測,定時自動化檢測頁面性能及異常
優化前
優化後
進度條平均加載(onload)時間(4G) | |
---|---|
優化前 | 4632ms |
優化後 | 2581ms 提高45% |
感謝耐心閱讀,歡迎你們交流,指正文中錯誤和疏漏,一塊兒學習!