上篇文章介紹了桌面瀏覽器的優化策略,相對於桌面瀏覽器,移動端 Web 瀏覽器上有一些較為明顯的特點:裝置螢幕小、新特性相容性比較好、支援一些較新的 HTML5 和 CSS3 特性、需要與 native 應用互動等。但移動端瀏覽器可用的 CPU 計算資源和網路資源極為有限,因此要做好移動端 Web 上的優化往往需要做更多的事情。首先,在移動端 Web 的前端頁面渲染中,桌面瀏覽器上的優化規則同樣適用,此外針對移動端也要做一些極致的優化來達到更好的效果。需要注意的是,並不是移動端的優化原則在桌面瀏覽器端就不適用,而是由於相容性和差異性的原因,一些優化原則在移動端更具代表性。
網路載入類
1.首屏資料請求提前,避免 JavaScript 檔案載入後才請求資料
為了進一步提升頁面載入速度,可以考慮將頁面的資料請求儘可能提前,避免在 JavaScript 載入完成後才去請求資料。通常資料請求是頁面內容渲染中關鍵路徑最長的部分,而且不能並行,所以如果能將資料請求提前,可以極大程度上縮短頁面內容的渲染完成時間。那怎麼將請求資料提前呢?建議採用首屏資料漸進式預載入的優化思路,具體如下:
1.優化首屏資料載入節點的速度。
2.預先載入首屏資料,使得多個序列節點並行化。
複製程式碼
接下來詳細介紹優化步驟,第1點會在第一步優化中體現,但核心思路和主要優化收益更多體現在第2點:多個序列節點並行化。
Step1:資原始檔下載與首屏資料請求節點並行
為了達到資源下載與資料請求並行的效果,我們充分利用了 HTTP Chunk 傳輸與瀏覽器的漸進式渲染特性:
將入口頁分為靜態片段和資料片段:靜態片段包含了各個資源標籤 (script,link)
,靜態的導航欄,載入指示器等;資料片段則是包含首屏資料的內聯指令碼,大至如下:
<script>window.__APP_DATA__ = { /* 相關的首屏資料 */ };</script>
複製程式碼
瀏覽器請求入口頁時,入口頁伺服器 並行 做以下操作:
-
HTTP Chunk 方式輸出靜態片段
-
請求首屏資料並在所有資料請求完成後將資料片段和應用初始化程式碼返回給瀏覽器。
注:http chunk 方式輸出在 NodeJS 中及其容易滿足,簡單的 res.write(chunk) 即可。
Step2:應用初始化,資原始檔下載,首屏資料請求節點並行
在 Step1 的基礎上繼續分析,應用初始化節點耗時也很明顯,同時該節點要進行必須等待資原始檔下載完畢,但理論上可以不依賴我們的首屏資料,還是可以讓其和首屏資料請求並行。這裡我們無法在 Step1 方案上直接將應用初始化和資料請求並行化,主要原因在於當首屏資料請求時間大於資源載入+應用初始化完成時間時,應用會在沒有資料的情況下進入首屏渲染節點,從而導致異常。
解決方案是將資料片段的輸出變成 promise 片段:
pending promise
片段,與靜態片段一起輸出,大概如下:
<script>
window.__APP_DATA__ = {
RESOLVERS: {}
userInfo: new Promise((resolve, reject) => {
// 超時認為失敗
let timer = setTimeout(reject.bind(null, {message: 'timeout'}), 12000);
window.__APP_DATA__.userInfo = (err, data) => {
clearTimeout(timer);
err ? reject(err) : resolve(data)
}
})
};
</script>
複製程式碼
resolve promise
片段,該片段在資料請求成功返回後輸出,大概如下:
<script>window.__APP_DATA__.RESOLVERS.userInfo(null, data); </script>
複製程式碼
reject promise
片段,該片段在資料請求失敗後輸出,大概如下:
<script>window.__APP_DATA__.RESOLVERS.userInfo(error); </script>
複製程式碼
2.首屏載入和按需載入,非首屏內容滾屏載入,保證首屏內容最小化
由於移動端網路速度相對較慢,網路資源有限,因此為了儘快完成頁面內容的載入,需要保證首屏載入資源最小化,非首屏內容使用滾動的方式非同步載入。一般推薦移動端頁面首屏資料展示延時最長不超過 3 秒。目前中國聯通 3G 的網路速度為 338KB/s(2.71Mb/s),所以推薦首屏所有資源大小不超過 1014KB,即大約不超過 1MB。
3.模組化資源並行下載
在移動端資源載入中,儘量保證 JavaScript 資源並行載入,主要指的是模組化 JavaScript 資源的非同步載入,例如 AMD 的非同步模組,使用並行的載入方式能夠縮短多個檔案資源的載入時間。
4.inline 首屏必備的 CSS 和 JavaScript
通常為了在 HTML 載入完成時能使瀏覽器中有基本的樣式,需要將頁面渲染時必備的 CSS 和 JavaScript 通過 <script>
或 <style>
內聯到頁面中,避免頁面 HTML 載入完成到頁面內容展示這段過程中頁面出現空白。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>樣例</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<style>
/*必備的首屏CSS*/
html,body{
margin:0;
padding:0;
background-color:#eee;
}
</style>
</head>
<body>
</body>
</html>
複製程式碼
5. meta dns prefetch 設定 DNS 預解析
設定檔案資源的 DNS 預解析,讓瀏覽器提前解析獲取靜態資源的主機 IP,避免等到請求時才發起 DNS 解析請求。通常在移動端 HTML 中可以採用如下方式完成。
<!--cdn域名預解析-->
<meta http-equiv="x-dns-prefetch-control" content="on" >
<link rel="dns-prefetch" href="//cdn.domain.com" >
複製程式碼
6.資源預載入
對於移動端首屏載入後可能會被使用的資源,需要在首屏完成載入後儘快進行載入,保證在使用者需要瀏覽時已經載入完成,這時候如果再去非同步請求就顯得很慢。
7.合理利用 MTU 策略
通常情況下,我們認為 TCP 網路傳輸的最大傳輸單元 (Maximum Transmission Unit,MTU)
為 1500B,即一個 RTT(Round-Trip Time
,網路請求往返時間)內可以傳輸的資料量最大為 1500 位元組。因此,在前後端分離的開發模式中,儘量保證頁面的 HTML 內容在 1KB 以內,這樣整個 HTML 的內容請求就可以在一個 RTT 內請求完成,最大限度地提高 HTML 載入速度。
快取類
1.合理利用瀏覽器快取
除了之前說到的使用 Cache-Control、Expires、Etag 和 Last-Modified 來設定 HTTP 快取外,在移動端還可以使用 localStorage 等來儲存 AJAX 返回的資料,或者使用 localStorage 儲存 CSS 或 JavaScript 靜態資源內容,實現移動端的離線應用,儘可能減少網路請求,保證靜態資源內容的快速載入。
2.靜態資源離線方案
對於移動端或 Hybrid 應用,可以設定離線檔案或離線包機制讓靜態資源請求從本地讀取,加快資源載入速度,並實現離線更新。關於這塊內容,我們會在後面的章節中重點講解。
3.嘗試使用 AMP HTML
AMP HTML 可以作為優化前端頁面效能的一個解決方案,使用 AMP Component 中的元素來代替原始的頁面元素進行直接渲染。
<!--不推薦-->
<video width="400" height="300" src="http://www.domain.com/videos/myvideo.mp4" poster="path/poster.jpg">
<div fallback>
<p>Your browser doesn’t support HTML5 video</p>
</div>
<source type="video/mp4" src="foo.mp4">
<source type="video/webm" src="foo.webm">
</video>
<!--推薦-->
<amp-video width="400" height="300" src="http://www.domain.com/videos/myvideo.mp4"
poster="path/poster.jpg">
<div fallback>
<p>Your browser doesn’t support HTML5 video</p>
</div>
<source type="video/mp4" src="foo.mp4">
<source type="video/webm" src="foo.webm">
</amp-video>
複製程式碼
4.嘗試使用 PWA 模式
PWA(Progressive Web Apps)是 Google 提出的用前沿的 Web 技術為網頁提供 App 使用體驗的一系列方案。
圖片類
1.圖片壓縮處理
在移動端,通常要保證頁面中一切用到的圖片都是經過壓縮優化處理的,而不是以原圖的形式直接使用的,因為那樣很消耗流量,而且載入時間更長。
2.使用較小的圖片,合理使用 base64 內嵌圖片
在頁面使用的背景圖片不多且較小的情況下,可以將圖片轉化成 base64
編碼嵌入到 HTML
頁面或 CSS
檔案中,這樣可以減少頁面的 HTTP 請求數。需要注意的是,要保證圖片較小,一般圖片大小超過 2KB 就不推薦使用 base64 嵌入顯示了。
.class-name{
background-image : url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAALCAMAAABxsOwqAAAAYFBMVEWnxwusyQukxQudwQyZvgyhxAyfwgyxzAsUHQGOuA0aJAERGAFIXwSTugyEqgtqhghQZgUwQQIpOQKbuguVtQuKrAuCowp2kQlheghTbQZHWQU7SwVAVgQ6TgQlLwMeKwFOemyQAAAAVElEQVQI1y3JVRaAIAAF0UconXbvf5ei8HfPDIQQhBAAFE10iKig3SLRNN4SP/p+N08VC0YnfIlNWtqIkhg/TPYbCvhqdHAWRXPZSp3g3CWZvVLXC6OJA3ukv0AaAAAAAElFTkSuQmCC');
}
複製程式碼
3.使用更高壓縮比格式的圖片
使用具有較高壓縮比格式的圖片,如 webp(需要設計降級相容方案)等。在同等圖片畫質的情況下,高壓縮比格式的圖片體積更小,能夠更快完成檔案傳輸,節省網路流量。
<img src="https://user-gold-cdn.xitu.io/2017/12/29/160a1604ef450396" alt="webp格式圖片" >
複製程式碼
4.圖片懶載入
為了保證頁面內容的最小化,加速頁面的渲染,儘可能節省移動端網路流量,頁面中的圖片資源推薦使用懶載入實現,在頁面滾動時動態載入圖片。
<img data-src="https://user-gold-cdn.xitu.io/2017/12/29/160a1604f3efd980" alt="懶載入圖片" >
複製程式碼
5.使用 MediaQuery 或 srcset 根據不同螢幕載入不同大小圖片
在介紹響應式的章節中我們瞭解到,針對不同的移動端螢幕尺寸和解析度,輸出不同大小的圖片或背景圖能保證在使用者體驗不降低的前提下節省網路流量,加快部分機型的圖片載入速度,這在移動端非常值得推薦。
6.使用 iconfont 代替圖片圖示
在頁面中儘可能使用 iconfont 來代替圖片圖示,這樣做的好處有以下幾個:
-
使用 iconfont 體積較小,而且是向量圖,因此縮放時不會失真;
-
可以方便地修改圖片大小尺寸和呈現顏色。
但是需要注意的是,iconfont 引用不同 webfont 格式時的相容性寫法,根據經驗推薦儘量按照以下順序書寫,否則不容易相容到所有的瀏覽器上。
@font-face{
font-family:iconfont;
src:url("./iconfont.eot");
src:url("./iconfont.eot?#iefix") format("eot"),
url("./iconfont.woff") format("woff"),
url("./iconfont.ttf") format("truetype");
}
複製程式碼
7.定義圖片大小限制
載入的單張圖片一般建議不超過 30KB,避免大圖片載入時間長而阻塞頁面其他資源的下載,因此推薦在 10KB 以內。如果使用者上傳的圖片過大,建議設定告警系統,幫助我們觀察瞭解整個網站的圖片流量情況,做出進一步的改善。
8.強快取策略
對於一些 永遠不會變 的圖片可以使用強快取的方式快取在使用者的瀏覽器上。
JavaScript 指令碼類
1.儘量使用 id 選擇器
選擇器選擇頁面 DOM 元素時儘量使用 id 選擇器,因為 id 選擇器速度最快。
2.合理快取 DOM 物件
對於需要重複使用的 DOM 物件,要優先設定快取變數,避免每次使用時都要從整個 DOM 樹中重新查詢。
//不推薦
$('#mod.active').remove('active');
$('#mod.not-active').addClass('active');
//推薦
let $mod=$('#mod');
$mod.find('.active').remove('active');
$mod.find('.not-active').addClass('active');
複製程式碼
3.頁面元素儘量使用事件代理,避免直接事件繫結
使用事件代理可以避免對每個元素都進行繫結,並且可以避免出現記憶體洩露及需要動態新增元素的事件繫結問題,所以儘量不要直接使用事件繫結。
//不推薦
$('.btn').on('click',function(e){
console.log(this);
});
//推薦
$('body').on('click','.btn',function(e){
console.log(this);
});
複製程式碼
4.使用 touchstart 代替 click
由於移動端螢幕的設計, touchstart
事件和 click
事件觸發時間之間存在 300 毫秒的延時,所以在頁面中沒有實現 touchmove 滾動處理的情況下,可以使用 touchstart 事件來代替元素的 click 事件,加快頁面點選的響應速度,提高使用者體驗。但同時我們也要注意頁面重疊元素 touch 動作的點選穿透問題。
//不推薦
$('body').on('click','.btn',function(e){
console.log(this);
});
//推薦
$('body').on('touchstart','.btn',function(e){
console.log(this);
});
複製程式碼
5.避免 touchmove、scroll 連續事件處理
需要對 touchmove
、scroll
這類可能連續觸發回撥的事件設定事件節流,例如設定每隔 16ms(60 幀的幀間隔為 16.7ms,因此可以合理地設定為 16ms )才進行一次事件處理,避免頻繁的事件呼叫導致移動端頁面卡頓。
//不推薦
$('.scroller').on('touchmove','.btn',function(e){
console.log(this);
});
//推薦
$('.scroller').on('touchmove','.btn',function(e){
let self=this;
setTimeout(function(){
console.log(self);
},16);
});
複製程式碼
6.避免使用 eval、with,使用 join 代替連線符+,推薦使用 ECMAScript6 的字串模板
這些都是一些基礎的安全指令碼編寫問題,儘可能使用較高效率的特性來完成這些操作,避免不規範或不安全的寫法。
7.儘量使用 ECMAScript6+的特性來程式設計
ECMAScript6+ 一定程度上更加安全高效,而且部分特性執行速度更快,也是未來規範的需要,所以推薦使用 ECMAScript6+ 的新特性來完成後面的開發。
渲染類
1.使用 Viewport 固定螢幕渲染,可以加速頁面渲染內容
一般認為,在移動端設定 Viewport 可以加速頁面的渲染,同時可以避免縮放導致頁面重排重繪。在移動端固定 Viewport 設定的方法如下。
<!--設定viewport不縮放-->
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
複製程式碼
2.避免各種形式重排重繪
頁面的重排重繪很耗效能,所以一定要儘可能減少頁面的重排重繪,例如頁面圖片大小變化、元素位置變化等這些情況都會導致重排重繪。
3.使用 CSS3 動畫,開啟 GPU 加速
使用 CSS3 動畫時可以設定 transform:translateZ(0)
來開啟移動裝置瀏覽器的 GPU 圖形處理加速,讓動畫過程更加流暢,但需要注意的是,在 Native WebView 下 GPU 加速有機率產生 App Crash。
-webkit-transform:translateZ(0);
-ms-transform:translateZ(0);
-o-transform:translateZ(0);
transform:translateZ(0);
複製程式碼
4.合理使用 Canvas 和 requestAnimationFrame
選擇 Canvas 或 requestAnimationFrame 等更高效的動畫實現方式,儘量避免使用 setTimeout、setInterval 等方式來直接處理連續動畫。
5.SVG 代替圖片
部分情況下可以考慮使用 SVG 代替圖片實現動畫,因為使用 SVG 格式內容更小,而且 SVG DOM 結構方便調整。
6.不濫用 float
在 DOM 渲染樹生成後的佈局渲染階段,使用 float 的元素佈局計算比較耗效能,所以儘量減少 float 的使用,推薦使用固定佈局或 flex-box 彈性佈局的方式來實現頁面元素佈局。
7.不濫用 web 字型或過多 font-size 宣告
過多的 font-size 宣告會增加字型的大小計算,而且也沒有必要的。
8.做好指令碼容錯
指令碼容錯可以避免非正常環境的執行錯誤影響頁面的載入和不相關功能的使用
##架構協議類
1.嘗試使用 SPDY 和 HTTP2
在條件允許的情況下可以考慮使用 SPDY 協議來進行檔案資源傳輸,利用連線複用加快傳輸過程,縮短資源載入時間。HTTP2 在未來也是可以考慮嘗試的。
2.使用後端資料渲染
使用後端資料渲染的方式可以加快頁面內容的渲染展示,避免空白頁面的出現,同時可以解決移動端頁面 SEO 的問題。如果條件允許,後端資料渲染是一個很不錯的實踐思路。後面的章節會詳細介紹後端資料渲染的相關內容。
3.使用 NativeView 代替 DOM 的效能劣勢
可以嘗試使用 NativeView 的 MNV* 開發模式來避免 HTML DOM 效能慢的問題,目前使用 MNV* 的開發模式已經可以將頁面內容渲染體驗做到接近客戶端 Native 應用的體驗了。但需要避免 js Framework 和 native Framework 的頻繁互動。
世界上沒有十全十美的事情,在我們做到了極致優化的同時也付出了很大的代價,這也是前端優化的一個問題。理論上這些優化都是可以實現的,但是作為工程師我們也要明白懂得權衡。優化提升了使用者體驗,是資料載入更快,但是專案程式碼卻被打亂,非同步內容要拆分出來,首屏的一個雪碧圖可能要分成兩個,頁面專案程式碼維護成本成倍增加,專案結構也可能變得混亂。所以前期在設計構建、元件的解決方案時要解決好非同步的自動處理問題。任何一部分優化都可以做得很深入,但不一定都值得,在優化的同時也要儘量考慮價效比,這才是我們作為一名前端工程師處理前端優化時應該具有的正確思維。歡迎大家加入QQ 前端技術交流群
544587175