我們經常會看到,一些站點在首次進入的時候會先顯示一個進度條,等資源載入完畢後再呈現頁面,大概像這樣:
然後整個頁面的操作就會非常流暢,因為之後沒必要再等待載入資源了。尤其是在移動端,或者是頁遊中,這樣做能避免頁面出現白屏(等待載入圖片),很大程度提升使用者體驗。那這種技術是如何實現的呢?其實非常簡單,本文就來從基礎細節探究一番。
為什麼需要資源預載入
大多時候,我們的頁面並不是一次渲染完畢的,而是隨著使用者的操作,不斷修改DOM節點,如果你動態插入了一個圖片節點,那麼瀏覽器要馬上發一個http請求,把圖片載入下來然後渲染在頁面上,如果使用者此時的網速不佳,那麼載入這張圖片可能就會消耗幾秒鐘時間,此時頁面上什麼都沒有(白屏)。最壞的情況,如果你的應用圖片很多,半天載入不出幾張圖,使用者很可能就在白屏的那幾秒跳走了。在遊戲中更嚴重,主角的圖片如果載入不出來,讓使用者玩空氣去?
除了在DOM中插入圖片節點,其他凡是涉及到要展示一張新圖片的操作,瀏覽器都得即時去請求圖片。比如,為某個節點新增如下css類來增加背景圖片:
1 2 3 |
.bg1{ background: url(http://p2.qhimg.com/t01ed1438874f940dc0.jpg); } |
或者是動態修改了src屬性、在canvas繪製圖片等,這些都會即時請求新資源。
那麼,資源預載入為什麼能解決上述問題呢?
我們預載入的資源,瀏覽器會快取下來,再次使用的時候,瀏覽器會檢查是不是已經在快取中,如果在,則直接用快取的資源,不傳送請求,或者由服務端返回304 not modified(304只帶請求頭資訊,不傳輸資源)。這樣使用一張圖片的時間會大大縮減,我們的頁面看起來會非常流暢,媽媽再也不用擔心使用者會跳走了~
也就是說,預載入的資源我們並不需要手動儲存,由瀏覽器自動放到快取了。
資源預載入的場景
什麼樣的專案需要預載入資源呢?
範圍應該鎖定單頁面應用,SPA的檢視一般都是一步一步來呈現的,各種資源通過非同步請求來獲取,為了追求原生app般的流暢體驗,可以把一些資源預載入下來。當然對於一些業務相關的圖片資源,也可考慮延遲載入,但延遲載入不是本文討論的範疇。
檢視/圖片較多的專題頁面,或者是需要逐幀圖片來完成的動畫效果,最好都要做預載入。
HTML5遊戲,圖片一般都比較多,而且很多逐幀動畫,必須要預載入,事實上一些遊戲引擎都會提供相應功能。
哪些資源需要預載入呢?
web中包含的資源有很多種,圖片、音視訊之類的媒體檔案,另外就是js、css檔案,這些都需要傳送請求來獲取。那這些資源難道我們都預載入?
當然不是了,預載入也是需要消耗時間的,總不能讓使用者等你載入個幾十秒鐘吧。具體預載入哪些資源,需要基於具體的考慮,也跟你的專案相關。以下是一些我的想法:
js、css檔案不需進行預載入。現在寫js基本都用requirejs之類的載入器,而且最後都會進行壓縮合並,將請求數降到最低,最終只有一個檔案,有些團隊甚至還將壓縮後的程式碼直接放在行內,這樣一個多餘的請求都沒有了。
那麼需要預載入的就是媒體檔案了,圖片、音視訊之類。這類資源也得根據實際情況來選擇哪些需要預載入。比如大多數頁面裝飾性圖片就需要預載入,而由業務動態獲取的圖片則無法預載入(預先不知道地址)。用作音效、小動畫的音視訊可以預載入,一個半小時長的視訊就不能預載入了。
預載入的原理與載入進度的獲取
上面都是紙上談兵的一些觀點,下面我們該從技術的角度來思考一下預載入該如何實現。
原理其實也相當簡單,就是維護一個資源列表,挨個去載入列表中的資源,然後在每個資源載入完成的回撥函式中更新進度即可。
以圖片為例,大致的程式碼應該是這樣:
1 2 3 4 |
var image = new Image(); image.onload = function(){}; image.onerror = function(){}; image.src = url; |
這樣就OK啦,圖片已經進快取,留著以後使用吧。
再說進度,這個進度嚴格來講並不是檔案載入的實時進度,因為我們只能在每個檔案載入完成的時候執行回撥,無法像timeline中那樣拿到檔案載入的實時進度。
計算方法就很簡單了,當前載入完的資源個數/資源總數*100,就是載入進度的百分比了。
資源預載入小外掛:resLoader.js介紹
本文的重點終於來了。。。額
根據上面的原理,我寫了一個外掛,用來做資源預載入。
具備的特徵如下:
1. 自定義資源列表,用於預載入
2. 自定義onProgress,想展示成進度條還是百分比數字還是個性的設計都可
3. 開始和結束可配置回撥函式
4. 只支援圖片的預載入
5. 支援amd、cmd載入器載入,同時支援直接用<script>標籤引入使用
6. 不依賴其他庫
用法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
var loader = new resLoader({ resources : [ 'http://p2.qhimg.com/t01ed1438874f940dc0.jpg', 'http://p9.qhimg.com/t01b4ff03b72c7dc6c7.jpg', 'http://p2.qhimg.com/t01dd90dfbec92074d0.jpg', 'http://p7.qhimg.com/t01cfec6d87cde457c5.jpg', 'http://p9.qhimg.com/t01943ced462da67833.jpg', 'http://p0.qhimg.com/t01943ced462da67833.jpg', 'http://p6.qhimg.com/t01aa15a7ba7ccb49a7.jpg', 'http://p8.qhimg.com/t010f1e8badf1134376.jpg', 'http://p8.qhimg.com/t01cf37ea915533a032.jpg', 'http://p3.qhimg.com/t0193d8a3963e1803e9.jpg', 'http://p3.qhimg.com/t01cd6a4d4b4bd4457b.jpg' ], onStart : function(total){ console.log('start:'+total); }, onProgress : function(current, total){ console.log(current+'/'+total); var percent = current/total*100; $('.progressbar').css('width', percent+'%'); $('.progresstext .current').text(current); $('.progresstext .total').text(total); }, onComplete : function(total){ alert('載入完畢:'+total+'個資源'); } }); loader.start(); |
各項引數都直接明瞭,不再多說了。在上面的例子中,我自己定義onProgress函式,做了一個簡單的進度條,你也可以做其他實現。函式為你傳入了current和total,分別表示當前完成的資源個數和資源總個數,可用於計算進度。
效果可看線上demo:點選這裡
另外附上下載地址,感興趣的朋友可以拿去使用:http://files.cnblogs.com/files/lvdabao/resLoader.zip
再多說兩句,關於xhr2新特性
前邊的廢話貌似有點多。。。想直接用外掛的下載下去用就好,有問題在此留言討論。
這裡想多說的東西是關於載入進度的,我上面說了我們只能獲取到的是進度其實是離散的點,不是連續的。其實利用HTML5的xhr2的新特性我們也可以嘗試獲取更加精確的進度。因為xhr2新增了一個非常有趣的特性:可以從服務端獲取檔案資料。我們以前從服務端返回的資料都是字串,現在可以直接返回Blob型別的檔案。那麼在這裡做一個猜想,能不能利用此特性,做更加精確的進度計算呢?我在此處只是提出一種可能性,還未做實踐。我們知道xhr2新增的upload屬性可以讓我們獲取到檔案上傳的進度資訊,但對於返回的資料,卻無法直接提供進度資訊,所以要想依靠它來實現還得做其他工作。