我們經常會看到,一些站點在首次進入的時候會先顯示一個進度條,等資源加載完畢后再呈現頁面,大概像這樣:
然后整個頁面的操作就會非常流暢,因為之后沒必要再等待加載資源了。尤其是在移動端,或者是頁游中,這樣做能避免頁面出現白屏(等待加載圖片),很大程度提升用戶體驗。那這種技術是如何實現的呢?其實非常簡單,本文就來從基礎細節探究一番。
為什么需要資源預加載
大多時候,我們的頁面並不是一次渲染完畢的,而是隨着用戶的操作,不斷修改DOM節點,如果你動態插入了一個圖片節點,那么瀏覽器要馬上發一個http請求,把圖片加載下來然后渲染在頁面上,如果用戶此時的網速不佳,那么加載這張圖片可能就會消耗幾秒鍾時間,此時頁面上什么都沒有(白屏)。最壞的情況,如果你的應用圖片很多,半天加載不出幾張圖,用戶很可能就在白屏的那幾秒跳走了。在游戲中更嚴重,主角的圖片如果加載不出來,讓用戶玩空氣去?
除了在DOM中插入圖片節點,其他凡是涉及到要展示一張新圖片的操作,瀏覽器都得即時去請求圖片。比如,為某個節點添加如下css類來增加背景圖片:
.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之類的加載器,而且最后都會進行壓縮合並,將請求數降到最低,最終只有一個文件,有些團隊甚至還將壓縮后的代碼直接放在行內,這樣一個多余的請求都沒有了。
那么需要預加載的就是媒體文件了,圖片、音視頻之類。這類資源也得根據實際情況來選擇哪些需要預加載。比如大多數頁面裝飾性圖片就需要預加載,而由業務動態獲取的圖片則無法預加載(預先不知道地址)。用作音效、小動畫的音視頻可以預加載,一個半小時長的視頻就不能預加載了。
預加載的原理與加載進度的獲取
上面都是紙上談兵的一些觀點,下面我們該從技術的角度來思考一下預加載該如何實現。
原理其實也相當簡單,就是維護一個資源列表,挨個去加載列表中的資源,然后在每個資源加載完成的回調函數中更新進度即可。
以圖片為例,大致的代碼應該是這樣:
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. 不依賴其他庫
用法如下:
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屬性可以讓我們獲取到文件上傳的進度信息,但對於返回的數據,卻無法直接提供進度信息,所以要想依靠它來實現還得做其他工作。