在做h5移動(dòng)頁面,相信大家一定碰到過頁面已經(jīng)打開,但是里面的圖片還未加載出來的情況,這種問題雖然不影響頁面的功能,但是不利于用戶體驗(yàn)。拋開網(wǎng)速的原因,解決這個(gè)問題有多方面的思路:最基本的,要從http請求合并,緩存管理,圖片壓縮等方面做性能優(yōu)化;另外就是可以對頁面里用到的所有圖片做預(yù)加載的處理,當(dāng)用戶打開頁面的時(shí)候不立即顯示第一屏,而是先顯示資源加載效果,等到加載完畢,再來顯示頁面的主內(nèi)容,這樣就能解決那個(gè)問題。雖然這種加載效果占用了用戶的瀏覽時(shí)間,但是我們可以把它做的好看有趣一點(diǎn),所以也不會(huì)影響用戶體驗(yàn)。本文實(shí)踐了這種想法,提供一個(gè)非常簡潔的圖片預(yù)加載組件,實(shí)現(xiàn)簡單,功能不弱,在做移動(dòng)頁面的時(shí)候應(yīng)該對你有參考價(jià)值。
效果:

1. 實(shí)現(xiàn)思路
html里面的img標(biāo)簽和css中background-imag等都會(huì)觸發(fā)瀏覽器去加載相關(guān)的圖片,但是如果這個(gè)圖片已經(jīng)加載過了的話,瀏覽器就會(huì)直接使用這張已經(jīng)加載好的圖片,從而能夠瞬間在頁面中渲染出來。通過javascript,創(chuàng)建Image對象,然后把這些對象的src屬性設(shè)置成要加載的圖片地址也能觸發(fā)瀏覽器加載圖片,利用這一點(diǎn)就能實(shí)現(xiàn)圖片預(yù)加載的功能:在頁面里首先把那些用到了相關(guān)的圖片的元素給藏掉,然后用js去加載圖片,等到所有圖片加載完畢再把藏掉的元素顯示即可。不過這僅僅是一個(gè)基本的實(shí)現(xiàn)思路,要完成一個(gè)功能較健壯的預(yù)加載組件,還有以下三個(gè)問題:
1)進(jìn)度問題
由于預(yù)加載的同時(shí),還得做一個(gè)預(yù)加載的效果,這就需要把加載的進(jìn)度實(shí)時(shí)通知到外部上下文才行。關(guān)于進(jìn)度有兩個(gè)實(shí)現(xiàn)方式,第一是已加載的數(shù)據(jù)大小/總的數(shù)據(jù)大小,第二是已加載的文件數(shù)/總的文件數(shù),在瀏覽器里面,采用第一種方式是不現(xiàn)實(shí)的,根本沒有原生的辦法可以做到,所以只能采用第二種。
2)圖片加載失敗的問題
比如說有4張圖片,已經(jīng)加載了50%,在加載第三張的時(shí)候出錯(cuò)了,該不該將進(jìn)度反饋成75%呢?答案是:應(yīng)該。如果不這么處理的話,進(jìn)度永遠(yuǎn)無法到100%,頁面主內(nèi)容就沒機(jī)會(huì)顯示了,雖然圖片加載有失敗的情況,但是跟加載器沒有關(guān)系,也許圖片本身就不存在呢?也就是說圖片加載失敗不應(yīng)該影響加載器的功能。
3)圖片加載超時(shí)的問題
圖片不能加載太久,否則用戶一直停留在加載效果上看不到主內(nèi)容,用戶的等待時(shí)間不可控制地延長,導(dǎo)致用戶體驗(yàn)下降,這樣就有悖加載器的初衷了。所以應(yīng)該給每個(gè)圖片設(shè)置一個(gè)加載的超時(shí)時(shí)間,如果在所有圖片的超時(shí)時(shí)間之后,還沒加載完,就應(yīng)該主動(dòng)放棄加載,通知外部上下文加載完畢,顯示主內(nèi)容。
綜合以上這些需求,本文提供的實(shí)現(xiàn)是:
JavaScript Code復(fù)制內(nèi)容到剪貼板
- (function () {
- function isArray(obj) {
- return Object.prototype.toString.call(obj) === '[object Array]';
- }
-
-
-
-
-
- var loader = function (imgList, callback, timeout) {
- timeout = timeout || 5000;
- imgList = isArray(imgList) && imgList || [];
- callback = typeof(callback) === 'function' && callback;
- var total = imgList.length,
- loaded = 0,
- imgages = [],
- _on = function () {
- loaded < total && (++loaded, callback && callback(loaded / total));
- };
- if (!total) {
- return callback && callback(1);
- }
- for (var i = 0; i < total; i++) {
- imgages[i] = new Image();
- imgages[i].onload = imgages[i].onerror = _on;
- imgages[i].src = imgList[i];
- }
-
-
-
-
- setTimeout(function () {
- loaded < total && (loaded = total, callback && callback(loaded / total));
- }, timeout * total);
- };
- "function" === typeof define && define.cmd ? define(function () {
- return loader
- }) : window.imgLoader = loader;
- })();
使用方式(對應(yīng)代碼中的test.html):
XML/HTML Code復(fù)制內(nèi)容到剪貼板
- <script src="../js/imgLoader.js"></script>
- <script>
- imgLoader(['../img/page1.jpg', '../img/page2.jpg', '../img/page3.jpg'], function(percentage){
- console.log(percentage)
- });
- </script>
運(yùn)行結(jié)果:

2. demo說明
本文開篇給出的效果,對應(yīng)的頁面是index.html,關(guān)于這個(gè)效果還有兩個(gè)問題需要說明:
1)它用了之前這篇博客Hammer.js+輪播原理實(shí)現(xiàn)簡潔的滑屏功能介紹的滑屏思路,并把它的一些邏輯包裝在了swipe.js,對外提供了一個(gè)全局變量Swipe,這個(gè)模塊有一個(gè)init的方法,以便外部通過調(diào)用Swipe.init()就能初始化滑屏相關(guān)的功能,原來沒有提供這個(gè)init方法,在js加載完畢就會(huì)初始化滑屏功能,有了這個(gè)init方法就可以把滑屏的邏輯延遲到加載完畢的時(shí)候去初始化。index.html一共引用了5個(gè)js:
XML/HTML Code復(fù)制內(nèi)容到剪貼板
- <script src="js/zepto.js"></script>
- <script src="js/transition.js"></script>
- <script src="js/hammer.js"></script>
- <script src="js/imgLoader.js"></script>
- <script src="js/swipe.js"></script>
其中imgLoader.js就是前面介紹圖片加載器的實(shí)現(xiàn),前三個(gè)js都是為最后一個(gè)swipe.js服務(wù)的,感興趣的可以繼續(xù)我的博客利用輪播原理結(jié)合hammer.js實(shí)現(xiàn)簡潔的滑屏功能了解相關(guān)內(nèi)容。不過滑屏不是本文的重點(diǎn),不了解swipe.js不會(huì)影響理解本文的內(nèi)容~
2)雖然我在demo中用到了3張比較大的圖片,但是由于在本地環(huán)境,加載速度還是非常快,所以一開始的時(shí)候,很難看到預(yù)加載的效果,最后只能想辦法在每個(gè)進(jìn)度回調(diào)之前做一下延遲,這才可以看到前面gif圖片一開始的那個(gè)loading效果,實(shí)現(xiàn)方式是:
XML/HTML Code復(fù)制內(nèi)容到剪貼板
- //模擬加載慢的效果
- var callbacks = [];
- imgLoader(['img/page1.jpg', 'img/page2.jpg', 'img/page3.jpg'], function (percentage) {
- var i = callbacks.length;
- callbacks.push(function(){
- setTimeout(function(){
- var percentT = percentage * 100;
- $('#loader__info').html('Loading ' + (parseInt(percentT)) + '%');
- $('#loader__progress')[0].style.width = percentT + '%';
- if (percentage == 1) {
- setTimeout(function(){
- $('#loader').remove();
- Swipe.init();
- }, 600);
- }
- callbacks[i + 1] && callbacks[i + 1]();
- },600);
- });
- if(percentage == 1) {
- callbacks[0]();
- }
- });
在真實(shí)環(huán)境,最好還是不要刻意去加這種延遲,沒必要為了讓用戶看到一個(gè)好看有趣的加載效果,就浪費(fèi)它不必要的等待時(shí)間,所以真實(shí)環(huán)境還是應(yīng)該用下面的代碼:
XML/HTML Code復(fù)制內(nèi)容到剪貼板
- imgLoader(['img/page1.jpg', 'img/page2.jpg', 'img/page3.jpg'], function (percentage) {
- var percentT = percentage * 100;
- $('#loader__info').html('Loading ' + (parseInt(percentT)) + '%');
- $('#loader__progress')[0].style.width = percentT + '%';
- if (percentage == 1) {
- $('#loader').remove();
- Swipe.init();
- }
- });
3. 注意事項(xiàng)
預(yù)加載是一種比較常見的實(shí)現(xiàn)效果,但是在使用的時(shí)候,有些問題需要注意:
1)什么時(shí)候用
頁面大的時(shí)候用,一般頁面大小超過3M就該考慮使用;頁面內(nèi)包含數(shù)據(jù)量比較大的圖片,在手機(jī)端測試能夠明顯看到加載緩慢的時(shí)候,可以考慮使用。
2)盡量使用sprite圖片
3)加載效果實(shí)現(xiàn)的時(shí)候,盡量不用圖片,即使要用也應(yīng)該用很小的圖片,否則加載效果卡在那就沒有意義了。
4. 總結(jié)
本文主要介紹了一個(gè)簡單的圖片預(yù)加載器,可應(yīng)用于h5移動(dòng)頁面的開發(fā)當(dāng)中,在它的思路之下,如果有必要的話,還可以對它進(jìn)行一些改造,用它來加載其它類型的資源,比如音頻或者視頻文件,畢竟這些類型的DOM對象也都有提供類似Image對象的屬性和回調(diào)。與預(yù)加載的方式相反的,還有一種圖片懶加載的技術(shù),現(xiàn)在網(wǎng)上已經(jīng)有比較好用的jquery插件了,不過還是很值的去深入了解下它的思路跟實(shí)現(xiàn)要點(diǎn),等我有時(shí)間去研究研究。同時(shí)感謝大家一直以來對腳本之家網(wǎng)站的支持!