背景
我們在做頁面的時候,從用戶體驗的角度出發,肯定是希望用戶以最快的速度看到完整的頁面信息,但在實際情況中經常會遇到些問題。
比如受網速影響,頁面加載素材的時間比較長,頁面會出現短時間的錯亂或者閃屏;
或者頁面素材比較多、比較大,需要一定的加載時間,特別有時候的活動頁面,我們一般會把首屏做的更多多樣化或者傳達比較豐富的氛圍的時候,我們的首屏素材上都會比較大,有時候也會給元素加上動畫來傳達信息,比如我之前做的WeGame新春活動頁就是這樣的情況;
一般我們的處理方案是在頁面素材加載完成之前顯示一個有趣(不那麼枯燥)的loading頁,那麼從重構的角度出發,我們這個素材加載狀態該怎麼配合loading頁做處理呢,於是想到了可以監測一下瀏覽器加載圖片的狀態,並同時響應處理,比如加載完時隱藏loading頁,每加載完一張圖片就更新加載的百分比進度條等。
當然,值得注意的是,我們應該儘量的讓用戶等待loading頁的時間變短,因爲不要忘了,loading頁也只是向後兼容處理的方案,它本身不是必須存在的。
方案分析
場景:除了首屏loading頁加載必須的圖片之外(無序加載),可能還有其他情景比如漫畫網站,需要加載完前一張圖片再加載下一張(有序加載)
原理:通過image 的 onload 事件判斷瀏覽器加載圖片的狀態。
實現:一般我們會這樣處理:
var img = new Image();
img.src = "logo.jpg";
img.onload = function () {
alert("image is loaded");
};
但是這樣處理有點問題,首先我們來認識兩點:
1.一旦Image對象設置了src值,瀏覽器就會向服務器發起請求,並緩存返回的圖片。
2.瀏覽器請求是異步的。也就是說這段代碼會遍歷數組,每張圖片幾乎同時發起請求,並不需要等待服務器返回結果後順序發起請求。就是說,js不會傻傻地在原地等待圖片的加載,而是繼續讀代碼,直到圖片加載完成,觸發onload事件,js纔會回來執行onload裏面的內容。
基於以上兩點,如果我們先賦值了圖片地址,瀏覽器已經加載完成的時候,onload綁定事件還沒有賦值上去,這樣就造成了一定的誤差,所以改進如下:
var img = new Image();
img.onload = function () {
alert("image is loaded");
};
img.src = "logo.jpg";
先綁定事件,在賦值圖片地址,確保瀏覽器發起請求前,圖片已經綁定了onload事件。
Preload插件實現
GitHub地址:https://github.com/xiangshuo1992/preload
/**
* 圖片預加載插件Preload
*
* @param array imgs 預加載的圖片地址數組列表
* @param Object options 配置參數
*/
(function ($) {
function Preload(imgs, options) {
this.imgs = (typeof imgs === 'string') ? [imgs] : imgs;
this.options = {
order: false, //默認值false,代表無序加載
minTimer: 0, //完成加載的最少時間,單位ms,默認爲0,一般展示類型的loading動畫會需要設置
each: null, //單張圖片加載完執行的方法,一般是修改進度狀態
end: null //所有圖片加載完執行的方法,一般是隱藏loading頁
};
this.timer = Date.now();
this.init(options);
};
//插件初始化
Preload.prototype.init = function (options) {
//配置參數合併
this.options = $.extend(this.options, options);
if (this.options.order) {
this.ordered(); //有序加載
} else {
this.unordered(); //無序加載
}
};
// 有序加載
Preload.prototype.ordered = function () {
var that = this,
imgs = this.imgs,
len = imgs.length,
count = 0,
options = this.options;
load();
function load() {
var img = new Image();
$(img).on('load error', function () {
options.each && options.each(count);
if (count >= len-1) {
//所有圖片加載完畢,檢查是否滿足最小loading時間
var timerCount = Date.now() - that.timer;
if (timerCount < options.minTimer) {
var timeout = options.minTimer - timerCount;
setTimeout(function () {
options.end && options.end();
}, timeout);
}else{
options.end && options.end();
}
} else {
load();
}
count++
});
// onload函數要寫在改變src前,這樣確保了onload函數一定會被調用
img.src = imgs[count];
}
};
// 無序加載
Preload.prototype.unordered = function () {
var that = this,
imgs = this.imgs,
len = imgs.length,
count = 0,
options = this.options;
for (var i = 0; i < len; i++) {
var img = new Image();
$(img).on('load error', function () {
options.each && options.each(count);
if (count >= len-1) {
//所有圖片加載完畢,檢查是否滿足最小loading時間
var timerCount = Date.now() - that.timer;
if (timerCount < options.minTimer) {
var timeout = options.minTimer - timerCount;
setTimeout(function () {
options.end && options.end();
}, timeout);
}else{
options.end && options.end();
}
}
count++;
})
img.src = imgs[i];
}
};
//擴展到jQuery對象上
$.extend($,{
preload: function (imgs, options) {
new Preload(imgs, options);
}
});
})(jQuery);
有序加載和無序加載
無序加載:前面我們說了,瀏覽器請求是異步的,所以我們的資源請求有可能是同時在加載過程中。
我用chrome瀏覽器的開發者工具,查看了無序加載時,圖片的加載狀態如下:
可以看到,會出現同時請求多張圖片的情況,也就是說當我們希望所有的網速集中在一張圖片上時,這個方案是不行的,於是就有了有序加載。
有序加載:前面我們是通過循環來實現無序加載的,我們要先實現圖片資源有序加載,就需要在當前圖片加載完成時纔去加載下一張圖片,這裏我們用函數回調實現,雖然整個資源的加載時間變長了,但是卻也保證了圖片是有序出現的,在上面說的漫畫網站的場景下非常適用。
我用chrome瀏覽器的開發者工具,查看了無序加載時,圖片的加載狀態如下:
說明文檔
Application.js
//= require jquery
...
//= require preload
Configuration
Name | Type | Default | Description |
---|---|---|---|
imgs | Array | [] | 預加載的圖片地址列表 |
options | Object | {} | 配置參數對象 |
Options
Name | Type | Default | Description |
---|---|---|---|
order | Boolean | false | 默認值false,代表無序加載 |
minTimer | Number | 0 | 完成加載的最少時間,單位ms,默認爲0,一般展示類型的loading動畫會需要設置 |
each | Function | null | 單張圖片加載完執行的方法,一般會修改進度狀態 |
end | Function | null | 所有圖片加載完執行的方法,一般會隱藏loading層 |
Examples
<style>
html,
body {
width: 100%;
}
.loading {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
text-align: center;
font-size: 30px;
background-color: #000;
}
</style>
<div class="box">
<img src="img/brief_1.jpg" alt="picture" id="img" style="width: 720px;height:400px;">
</div>
<div class="loading">
<p class="progress"></p>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="js/preload.js"></script>
<script>
var imgs = [
'img/brief_1.jpg',
'img/brief_2.jpg',
'img/brief_3.jpg',
'img/brief_4.jpg',
'img/brief_5.jpg',
'img/brief_6.jpg',
'img/brief_7.jpg',
'img/brief_8.jpg',
'img/brief_9.jpg',
'img/brief_10.jpg',
'img/brief_11.jpg'
];
var index = 0,
len = imgs.length,
progress = $('.progress');
//圖片預加載
$.preload(imgs, {
each: function (count) {
progress.html(Math.round((count+1) / len * 100) + '%');
},
end: function () {
$('.loading').hide();
}
});
</script>
總結
上面我們分析和實現了一個看起來非常簡單,但是也包含了不少內容的圖片預加載插件,從業務場景、方案分析、代碼實現、說明文檔、測試使用、總結輸出這一系列的流程,我知道了,實現一個插件可簡單,可複雜,可輕量,可豐富,不管怎樣,任何的實現都是基於滿足自己和團隊的使用,合適的就是最好的。
最後,也希望能給大家提供一點幫助,如有建議,歡迎留言!